bashspecial-variables

Iterating over the values of $@


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


Solution

  • 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 "$*.