2

I would like to iterate over the arguments given to a bash script, for example:

./bash_script file1 file2 file3

$@ gives me all the files given to the script but how do I iterate over the files?

I would like to cat each file and remove the contents using awk (I know how to do that, it's the unpacking of $@ that's got me confused).

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953

2 Answers2

5

The trick is to double quote it as in "$@".

foo(){
   printf ' ->%s\n' "$@";
}
foo "a b" c "d e"

is equivalent to:

printf ' ->%s\n' "a b" c "d e"

If the $@ in the above is not double-quoted, then you'll get:

 printf ' ->%s\n' a b c d e

due to word splitting on $IFS characters ( $IFS defaults to ' '$'\t'$'\n', i.e. a space, a tab, and a newline )


$@ vs. $*

Double quotes work like this for any @-expansion on any array:

$ foo=( "a b" c "d e" )
$ printf ' ->%s\n' "${foo[@]}"
   ->a b
   ->c
   ->d e

In contrast, *-expansions (e.g., $*, ${foo[*]}) will use the first character of $IFS to join the items of an array into a single string:

$ foo=( "a b" c "d e" )
$ ( IFS=:; printf ' ->%s\n' "${foo[*]}" )
 ->a b:c:d e

which, if left unquoted, will again split on this very IFS character:

$ foo=( "a b" c "d e:f:g" )
$ ( IFS=:; printf ' ->%s\n' ${foo[*]} ) 
 ->a b
 ->c
 ->d e
 ->f
 ->g

Trick for iterating over $@ in for-loops:

The "$@" array is special. If you want to iterate over "$@" in a for loop, then you can abbreviate

 for variable_name in "$@"; do
   ...
 done

as

 for variable_name; do 
 done

as skipping the in something part of a for loop implies in "$@".

This works even in POSIX-only shells too (dash, bourne shell) which don't have array variables but support "$@" and "$*.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
2

To iterate over them, you use a double-quoted $@, along the lines of:

for arg in "$@" ; do
    echo "--> ${arg}"
done

The for arg in "$@" could also be written as for arg (or even for arg in) since the bash man page states:

for name [ [ in [ word ... ] ] ; ] do list ; done

blah blah blah

If the in word is omitted, the for command executes list once for each positional parameter that is set.

However, I prefer the explicit variant.

The following transcript shows this in action:

pax: testprog.sh 1 2 3 '4 5' '' '6 7 8' "9"
--> 1
--> 2
--> 3
--> 4 5
--> 
--> 6 7 8
--> 9

And, just as an aside, your comment about using cat and awk to modify the files may bring down the wrath of the "useless use of cat award" crowd :-)

If you're thinking about something like:

cat "${fspec}" | awk 'do something'

then cat is totally unnecessary. You can instead use:

awk 'do something' "${fspec}"

I'm don't usually worry about the (small) inefficiencies of running an extra process but some people do.

Community
  • 1
  • 1
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953