9

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.

3 Answers 3

18
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).

Sign up to request clarification or add additional context in comments.

5 Comments

This is the correct answer to the specific question asked, but as chepner pointed out, it's a correct step down the wrong path.
@GordonDavisson Some people think about whether or not they could while others think about whether or not they should. I tend to be the first type. :D
but not for sh :'(
@kokbira A sh solution: Separate the command with a character that's not in the commands and then split on that character, e.g., COMMANDS='ls / df ~ du -hs ~/Devel/' ( IFS=' '; for c in $COMMANDS; do echo "$c"; done ) (uses the tab character as a separator).
@PSkocik, updating, I could use arrays on my bash scripts, so I fell in love with it
6

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!

1 Comment

I know your answer is more correct, but I marked the other one as it is more simple and enough in most cases. Thanks though !
1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.