7

I have a problem when looping over such a variable. I have prepared 2 examples to show the problem.

ex1:

#!/bin/bash
DIRS="$@"

for DIR in $DIRS; do
    echo "$DIR"
done

ex2:

#!/bin/bash
for DIR in "$@"; do
    echo "$DIR"
done

The second example works as expected (and required). A quick test follows:

$ ex1 "a b" "c"
a
b
c
$ ex2 "a b" "c"
a b
c

The reason, why I want to use the first method is because I want to be able to pass multiple directories to the program or none to use the current dir. Like so:

[ $# -eq 0 ] && DIRS=`pwd` || DIRS="$@"

So, how do I get example 1 to be space-safe?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Frank
  • 651
  • 4
  • 9

3 Answers3

10

Use an array instead of a simple variable.

declare -a DIRS

DIRS=("$@")

for d in "${DIRS[@]}"
do echo "$d"
done

This produces the result:

$ bash xx.sh a "b c" "d e f    g" h z
a
b c
d e f    g
h
z
$
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Aaah, I tried a lot and I was so close. I just forgot the quotes around ${DIRS[@]}. Thank you – Frank May 01 '11 at 23:01
  • @Frank: The array notations are a bit fussy - I have to go back to the reference manual each time I use them. I'm getting better, but there are many years of Bourne shell experience to override. – Jonathan Leffler May 01 '11 at 23:07
4

Why not use the default expansion feature?

for DIR in "${@:-$(pwd)}"; do
    echo $DIR
done
Mikel
  • 24,855
  • 8
  • 65
  • 66
0

One approach is to replace the spaces within arguments with something else, and then substitute spaces back again when you use the argument:

dirs="${@/ /###}"
for dir in $dirs; do
    echo "${dir/###/ }"
done

This relies on your being able to come up with some sequence of characters that you can be confident will never appear in a real file name.

For the specific situation you have, where you want to be able to choose between supplying an explicit list of directories or defaulting to the current directory, a better solution is probably to use a function:

do_something() {
    for dir in "$@"; do
        echo "$dir"
    done
}

if [ $# -eq 0 ]; then
    do_something .
else
    do_something "$@"
fi

Or possibly:

do_something() {
    echo "$1"
}

if [ $# -eq 0 ]; then
    do_something .
else
    for dir in "$@"; do
        do_something "$dir"
    done
fi
Ross Smith
  • 3,719
  • 1
  • 25
  • 22