I have a bash script for my RaspberryPi (running Raspbian) that is supposed to take multiple file names as parameter(s) and play one after the other (using omxplayer). The basic structure is this:

for f in ${*}
  echo "${f}";

Now I have problems when the input filenames contain spaces; in particular, the behaviour seems to be inconsistent. Assume we have two files test a b and test d e in the same directory. Running above script with different parameters yields this:

$ ./test test\ a\ b
$ ./test "test a b"
$ ./test test\ a*
$ ./test "test a*"

But, curiously:

./test "test*"
test a b
test d e

Obviously, only this last variant provides the intended output. It is cumbersome to use, however, especially if you want to watch a single file (tab-completion will fill in the whole name) or if one of the folder names in the file's path contains spaces.

What can I do differently in the shellscript so that it will always behave as intended? In particular, both of

$./test test*
$./test test\ a\ b test\ d\ e

should produce the same output

test a b
test d e    

so the script can be used easily using normal tab-completion.

  • In general, test is not a good name for an example program, because there is /usr/bin/test; It's relevant because people learn from seeing examples; And in this special case, it would be a lot more readable with a different name too. – Volker Siegel Oct 14 '14 at 11:55

Use "$@" instead of ${*} (see Special Parameters in the manual)

for f in "$@"; do 
    echo make sure you quote your "$variables" everywhere in the loop

There's a shorthand (and more portable) for this:

for f do ...

for f; do would also work in some shells but is not standard.

  • The for f; do trick does not work in my bash 4.2.39. But instead of using "$@" I can stay with the $* when I add a IFS='\n' before the for loop. – erik Feb 10 '13 at 19:03
  • IFS='\n' means that only linebreaks are separators. $IFS is by default set to space, tab and linebreak, i.e. IFS=' \t\n'. – erik Feb 10 '13 at 19:09
  • Both forms seem to work fine. I prefer the first version for clarity; in which way is it not as portable? Anyway, thanks! – Raphael Feb 10 '13 at 19:37
  • 1
    @Raphael, for i in "$@" doesn't work in many versions of the Bourne shell when $# is 0 (Which is why you often see the ${1+"$@"} construct). for f do is the Bourne shell way to loop over the arguments. – Stéphane Chazelas Feb 10 '13 at 20:02
  • 1
    Is the Bourne shell actually distributed these days? – glenn jackman Feb 10 '13 at 21:33

An alternative way is to use a while loop in one of two ways:

input_cmd|while read i; do
  echo "$i";

or through process substitution:

while read i; do
  echo "$i";
done < <(input_cmd)

The latter is a bashism as far as I'm aware, so it may not work in other bourne compatible shells.

Since you are getting the input from the command line, it may be better to stick to the for loop solution offered in the previous answer, however.

You should also have a look at man xargs, because that is usually the best way to handle exotic command line arguments.

  • 2
    the latter is a kshism, copied by zsh and bash. When you use read, you need the -r option and remove the white space characters from $IFS (like IFS= read -r i) unless you need the special behaviour of read if you don't – Stéphane Chazelas Feb 10 '13 at 22:16
  • But what if the filenames contain newlines? – ruakh Feb 11 '13 at 0:10
  • @ruakh: that's exactly when xargs comes in handy. Last paragraph of my answer. – 0xC0000022L Feb 11 '13 at 13:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.