5

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:

#!/bin/bash
for f in ${*}
do
  echo "${f}";
done;

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
a
b
$ ./test "test a b"
test
a
b
$ ./test test\ a*
test
a
b
$ ./test "test a*"
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.

1
  • 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. Commented Oct 14, 2014 at 11:55

2 Answers 2

12

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.

6
  • 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
    Commented Feb 10, 2013 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
    Commented Feb 10, 2013 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
    Commented Feb 10, 2013 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. Commented Feb 10, 2013 at 20:02
  • 1
    Is the Bourne shell actually distributed these days? Commented Feb 10, 2013 at 21:33
3

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

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

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.

3
  • 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 Commented Feb 10, 2013 at 22:16
  • But what if the filenames contain newlines?
    – ruakh
    Commented Feb 11, 2013 at 0:10
  • @ruakh: that's exactly when xargs comes in handy. Last paragraph of my answer. Commented Feb 11, 2013 at 13:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .