shell – Difference Between $* and $@

argumentskshquotingshell

Consider the following code:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4

It outputs:

1 2 3 4

1 2 3 4

I am using Ksh88, but I am interested in other common shells as well. If you happen to know any particularity for specific shells, please do mention them.

I found the follwing in the Ksh man page on Solaris:

The meaning of $* and $@ is
identical when not quoted or when used as a parameter
assignment value or as a file name. However, when used as a
command argument, $* is equivalent to “$1d$2d…'', where d
is the first character of the IFS variable, whereas $@ is
equivalent to $1 $2 ….

I tried modifying the IFS variable, but it doesn't modify the output. Maybe I'm doing something wrong?

Best Answer

When they are not quoted, $* and $@ are the same. You shouldn't use either of these, because they can break unexpectedly as soon as you have arguments containing spaces or wildcards.


"$*" expands to a single word "$1c$2c...". Usually c is a space, but it's actually the first character of IFS, so it can be anything you choose.

The only good use I've ever found for it is:

join arguments with comma (simple version)

join1() {
    typeset IFS=,
    echo "$*"
}

join1 a b c   # => a,b,c

join arguments with the specified delimiter (better version)

join2() {
    typeset IFS=$1   # typeset makes a local variable in ksh (see footnote)
    shift
    echo "$*"
}

join2 + a b c   # => a+b+c

"$@" expands to separate words: "$1" "$2" ...

This is almost always what you want. It expands each positional parameter to a separate word, which makes it perfect for taking command line or function arguments in and then passing them on to another command or function. And because it expands using double quotes, it means things don't break if, say, "$1" contains a space or an asterisk (*).


Let's write a script called svim that runs vim with sudo. We'll do three versions to illustrate the difference.

svim1

#!/bin/sh
sudo vim $*

svim2

#!/bin/sh
sudo vim "$*"

svim3

#!/bin/sh
sudo vim "$@"

All of them will be fine for simple cases, e.g. a single file name that doesn't contain spaces:

svim1 foo.txt             # == sudo vim foo.txt
svim2 foo.txt             # == sudo vim "foo.txt"
svim2 foo.txt             # == sudo vim "foo.txt"

But only $* and "$@" work properly if you have multiple arguments.

svim1 foo.txt bar.txt     # == sudo vim foo.txt bar.txt
svim2 foo.txt bar.txt     # == sudo vim "foo.txt bar.txt"   # one file name!
svim3 foo.txt bar.txt     # == sudo vim "foo.txt" "bar.txt"

And only "$*" and "$@" work properly if you have arguments containing spaces.

svim1 "shopping list.txt" # == sudo vim shopping list.txt   # two file names!
svim2 "shopping list.txt" # == sudo vim "shopping list.txt"
svim3 "shopping list.txt" # == sudo vim "shopping list.txt"

So only "$@" will work properly all the time.


typeset is how to make a local variable in ksh (bash and ash use local instead). It means IFS will be restored to its previous value when the function returns. This is important, because the commands you run afterward might not work properly if IFS is set to something non-standard.