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
5
  • Did you read all the way through Mikel's answer? Did you read the first half of Wojtek Rzepala's answer? Most importantly, did you read Carlos Campderrós's answer?  It's probably the best of the bunch, and it doesn't mention IFS at all. Commented Nov 26, 2018 at 20:56
  • 1
    Yeah, Stack Exchange's search engine sucks.  Half of the time I want to search Stack Exchange, I use Google; see, for example, this search. … P.S. Gilles's answer, although a late addition, is pretty good, too.  (Gilles's answers usually are.) Commented Nov 26, 2018 at 21:10
  • 1
    The fact that it's an assignment in your test is the key here. Splitting the value on the right hand side of an assignment doesn't make much sense, since the assigned value can only be a single string anyway. So the fact that "$@" assigns to separate words doesn't apply there. That's discussed in Unexpected outcome of a=“$@”.
    – ilkkachu
    Commented Nov 26, 2018 at 21:19
  • 1
    Right.  $@ is special.  Once you assign it to an ordinary variable, you lose that special quality.  But try using arrays: foo=("$@") / bar=("$*") / set | grep '^foo=' / set | grep '^bar='. Commented Nov 26, 2018 at 21:36
  • @Scott thanks. That entirely resolves things. It was somehow unsatisfying to think, oh, it just doesn't work when you assign it to a variable, which, I think added to the general opacity of bash.
    – De Novo
    Commented Nov 26, 2018 at 21:42

3 Answers 3

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.

2
  • ...or just printf '!%s!\n' "$*" vs printf '!%s!\n' "$@"
    – ilkkachu
    Commented Nov 26, 2018 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
    Commented Nov 26, 2018 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.

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.

Not the answer you're looking for? Browse other questions tagged .