Bash $* and $@

In Bash, there are two closely related "special parameters" for accessing how the current script was invoked: $* and $@. For both variables, the behavior is affected by whether or not the variable is enclosed in double quotes. The following table summarizes all cases:

Form Meaning
$* $1 $2 $3...
$@ $1 $2 $3...
"$*" "$1 $2 $3..."
"$@" "$1" "$2" "$3"...

Easy, right?

Without double quotes, the two do the same thing: they expand to each positional argument, and additional word splitting will be done on each argument as usual. With double quotes, the two have an extremely subtle, but important difference. Consider the following two program invocations:

# invoke foo.sh with two arguments
$ ./foo.sh hello world

# invoke foo.sh with one argument
$ ./foo.sh "hello world"

If foo.sh is implemented using $* or "$*", it won't be able to distinguish between these two cases. If it's using $*, then it will think there are two arguments in both cases, due to word splitting. If it's using "$*", it will look like one argument in both cases.

If instead foo.sh is implemented using "$@", the two cases are distinguishable.

This usually comes up when writing a wrapper script that passes its arguments down to another program. Here's a complete example:

#!/bin/bash
SHOWTIMES=0
OPTIND=1
while getopts "h?v" opt; do
    case "$opt" in
        h|\?)
            echo "usage: $0 [-v]"
            exit 0
            ;;
        v)
            SHOWTIMES=1
            ;;
    esac
done

# N.B. must use "$@" here, NOT $*
run() {
    if [ "$SHOWTIMES" -eq 1 ]; then
        time "$@"
    else
        "$@"
    fi
}

run cp "./expense report.xls" "./funny    name.txt" /tmp
run dig www.google.com
# etc...

If the script is run without any arguments, all of the lines that start with the run command will just run as usual. On the other hand, if the script is invoked with -v, all of the lines that start with run will be timed using the time shell builtin.

As shown in the example, it's possible to call run with arguments containing whitespace. The only way to ensure that such arguments are handled correctly is to use the quoted form "$@". As a fun bit of trivia: $@ is the only bash variable that can be split into words when quoted.

It's understandable if you find this confusing. My advice is to always use the quoted form "$@" if you're unsure. It's almost always what you want.