Short answer: use "$@"
(note the double quotes). The other forms are very rarely useful.
"$@"
is a rather strange syntax. It is replaced by all the positional parameters, as separate fields. If there are no positional parameters ($#
is 0), then "$@"
expands to nothing (not an empty string, but a list with 0 elements), if there is one positional parameter then "$@"
is equivalent to "$1"
, if there are two positional parameters then "$@"
is equivalent to "$1" "$2"
, etc.
"$@"
allows you to pass down the arguments of a script or function to another command. It is very useful for wrappers that do things like setting environment variables, preparing data files, etc. before calling a command with the same arguments and options that the wrapper was called with.
For example, the following function filters the output of cvs -nq update
. Apart from the output filtering and the return status (which is that of grep
rather than that of cvs
), calling cvssm
on some arguments behaves like calling cvs -nq update
with these arguments.
cvssm () { cvs -nq update "$@" | egrep -v '^[?A]'; }
"$@"
expands to the list of positional parameters. In shells that support arrays, there is a similar syntax to expand to the list of elements of the array: "${array[@]}"
(the braces are mandatory except in zsh). Again, the double quotes are somewhat misleading: they protect against field splitting and pattern generation of the array elements, but each array element ends up in its own field.
"$*"
always expands to one word. This word contains the positional parameters, concatenated with a space in between. (More generally, the separator is the first character of the value of the IFS
variable. If the value of IFS
is the empty string, the separator is the empty string.) If there are no positional parameters then "$*"
is the empty string, if there are two positional parameters and IFS
has its default value then "$*"
is equivalent to "$1 $2"
, etc.
$@
and $*
outside quotes are equivalent. They expand to the list of positional parameters, as separate fields, like "$@"
; but each resulting field is then split into separate fields which are treated as file name wildcard patterns, as usual with unquoted variable expansions.
For example, if the current directory contains three files bar
, baz
and foo
, then:
set -- # no positional parameters
for x in "$@"; do echo "$x"; done # prints nothing
for x in "$*"; do echo "$x"; done # prints 1 empty line
for x in $*; do echo "$x"; done # prints nothing
set -- "b* c*" "qux"
echo "$@" # prints `b* c* qux`
echo "$*" # prints `b* c* qux`
echo $* # prints `bar baz c* qux`
for x in "$@"; do echo "$x"; done # prints 2 lines: `b* c*` and `qux`
for x in "$*"; do echo "$x"; done # prints 1 lines: `b* c* qux`
for x in $*; do echo "$x"; done # prints 4 lines: `bar`, `baz`, `c*` and `qux`