Reputation: 2571
I would like to do something like this:
COMMANDS='"ls /" "df ~" "du -hs ~/Devel/"'
for i in $COMMANDS; do
echo $i
done
Where the result would be:
ls /
df ~
du -hs ~/Devel/
But I can't find the right syntax for the spaces.
Upvotes: 9
Views: 11936
Reputation: 303
When there's space issues, take a step back with an eval command that'll help separate spaces/single-quotes (outside of quotes, unescaped) from meta-level spaces/single-quotes (in quotes or escaped).
eval "spacefulAnswer=($(command that produces quoted items with spaces))"
for i in "${spacefulAnswer[@]}"; do echo "$i"; done
Example:
echo Hello > toto.txt
echo '"Second Line"' >> toto.txt
echo 'Third\ Line' >> toto.txt
Note that the second line contents are quoted on purpose. Also the third line isn't quoted but the space is escaped. Now toto.txt contains: cat toto.txt
Hello
"Second Line"
Third\ line
Note that cat toto.txt
yields the same kind of result as an ls
that would list the files of a directory that would contain three files with those names.
Now let's load our array with spaceful items:
eval "totospc=($(cat toto.txt))"
Here note that, thanks to eval
, quotes and escaped spaces have been taken of for what they would have been if you had entered them in the command-line i.e., by taking quoting and escaping into account.
for i in "${totospc[@]}"; do echo "$i"; done
This yields three lines, not five:
Hello
Second Line
Third Line
Upvotes: 1
Reputation: 531215
I'd recommend you not do that at all. First, it's much longer and more complicated than simply writing
ls /
df ~
du -hs ~/Devel/
Second, flat strings are not able to store nested, space-delimited strings. There is no way (and yes, I'm ignoring eval
) to differentiate between spaces that separate commands and spaces that separate arguments within a command. You can use an array for simple commands, but you can't nest arrays, so as soon as the arguments for one of your commands contain spaces, you are back to the original problem.
commands=("ls /" "df ~" "du -hs ~/Devel") # OK, but...
commands=("ls \"foo bar\"" "echo 'hello world'") # No.
If you want your script to be able to run arbitrary commands specified by a user, have it source files from a known directory instead (that is, implement a plug-in system).
command_dir=~/myscript_plugins
for f in "$command_dir"; do
source "$f"
done
where $command_dir
contains one file for each of the commands you want to run.
Or, define a series of functions, and store their names in an string (function names can't contain spaces, so there's no need for arrays):
lister () { ls /; }
dfer () { df ~; }
duer () { du -hs ~/Devel; }
commands="lister dfer duer"
for command in $commands; do
$command
done
or
commands=(lister dfer duer)
for command in "${commands[@]}"; do
$command
done
Further reading: I'm trying to put a command in a variable, but the complex cases always fail!
Upvotes: 6
Reputation: 60068
COMMANDS=("ls /" "df ~" "du -hs ~/Devel/")
for i in "${COMMANDS[@]}"; do
echo "$i"
done
This uses an array to store the commands. This feature is also available in ksh
, zsh
, but not in sh
.
Arrays behave like the "$@"
argument array. Applying a for
loop on "${ARRAY_NAME[@]}"
(the quotes are important) will give you each item in succession. If you omit the quotes, it'll all get smushed together and split on the separators present in your IFS
environment variable ('\t'
, '\n'
and ' '
by default).
Upvotes: 18