0

The bash manual says:

Regarding: $*

When the expansion occurs within double quotes, it expands to a single word with the value of each parameter separated by the first character of the IFS special variable. That is, "$*"is equivalent to "$1c$2c...", where c is the first character of the value of the IFS variable.

Regarding: $@

When the expansion occurs within double quotes, each parameter expands to a separate word. That is, "$@" is equivalent to "$1" "$2" ....

Provided the first character of the value of the IFS variable is in fact a single space, I can't seem to come up with an example where these two special parameters would produce different behavior. Can anyone provide me with an example (again, without changing IFS) where they would produce different behavior?

My own test, which still baffles me a bit, is as follows:

#!/usr/bin/env bash
# File: test.sh
# set foo and bar in the global environment to $@ and $*

test_expansion () {
  foo="$@"
  bar="$*"
}

Now testing:

. test.sh
test_expansion a b c d
# foo is $@
# bar is $*

for e in "$foo"; do
  echo "$e"
done
# a b c d

for e in "$bar"; do
  echo "$e"
done
# a b c d
muru
  • 72,889
De Novo
  • 115

3 Answers3

3

The difference comes in when you have arguments you pass in on the command line with IFS characters in them (e.g. an argument with a space). To see the difference, look at this script:

#!/bin/bash

echo 'Here is $*'
for x in "$*"; do
    echo "  !${x}!"
done

echo ""
echo 'And here is $@'
for x in "$@"; do
    echo "  !${x}!"
done

exit 0

Now, look at the difference when you pass in an argument with a space.

./testspace01.sh "This is" a test
Here is $*
  !This is a test!

And here is $@
  !This is!
  !a!
  !test!

UPDATE Ah, assigning what was passed in on the command line to a variable brings its own little quirks. :)

Remember, everything that was passed in on the command line is an array. So, assigning the array to a string gives you something different than signing to an array. And, handling the array is different depending on if you are using the star or the asterisk. Here is an updated version of my script.

#!/bin/bash

s_star="$*"
echo 'Here is s_star'
for x in "${s_star}"; do
    echo "  !${x}!"
done

a_star=("$*")
echo ""
echo 'Here is a_star'
for x in "${a_star}"; do
    echo "  !${x}!"
done

s_at="$@"
echo ""
echo 'Here is s_at'
for x in "${s_at}"; do
    echo "  !${x}!"
done

a_at=("$@")
echo ""
echo 'Here is a_at (using star)'
for x in "${a_at[*]}"; do
    echo "  !${x}!"
done

echo ""
echo 'Here is a_at (using at)'
for x in "${a_at[@]}"; do
    echo "  !${x}!"
done

exit 0

Here is the output:

./testspace02.sh "This is" a test
Here is s_star
  !This is a test!

Here is a_star
  !This is a test!

Here is s_at
  !This is a test!

Here is a_at (using star)
  !This is a test!

Here is a_at (using at)
  !This is!
  !a!
  !test!

As you can see, there is different behaviors.

Rui F Ribeiro
  • 56,709
  • 26
  • 150
  • 232
Lewis M
  • 1,048
  • ...or just printf '!%s!\n' "$*" vs printf '!%s!\n' "$@" – ilkkachu Nov 26 '18 at 21:12
  • This acts as I expected it would on reading the documentation, but when I ran my own test (putting the values for "$@" and "$*" into a global variable and then manipulating them in an interactive session), it doesn't behave this way. – De Novo Nov 26 '18 at 21:18
3

Try this:

#!/bin/bash
show-difference () {
    for e in "$@" ; do
        printf '<%s>\n' "$e"
    done
    for e in "$*" ; do
        printf '[%s]\n' "$e"
    done
}

show-difference {a..h}

Output:

<a>
<b>
<c>
<d>
<e>
<f>
<g>
<h>
[a b c d e f g h]

"$*" is a single word, while "$@" expands to all the parameters as single words. Moreover, "$@" works correctly even if the arguments contain the first character of IFS.

choroba
  • 47,233
0
set -- "hello there" bumblebee

printf '%s\n' "$@"
printf '%s\n' "$*"

Result:

hello there
bumblebee

followed by

hello there bumblebee

This shows that "$@" generates a list of individually quoted elements while "$*" generates a single quoted string.

Using bash, this can also be illustrated by a short shell script that takes a number of command line arguments:

#!/bin/bash

IFS='_'

atarray=( "$@" )
stararray=( "$*" )

printf 'at: %s\n' "${atarray[@]}"
printf 'star: %s\n' "${stararray[@]}"

Running it:

$ bash ./script.sh "one two" three four
at: one two
at: three
at: four
star: one two_three_four

This also shows that using "$*" will use the value in $IFS (the first character only) to delimit the elements in the string resulting from the expansion.

Kusalananda
  • 333,661