0

This question already has an answer here:

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

marked as duplicate by Scott, ilkkachu bash Nov 26 '18 at 21:15

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 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. – Scott Nov 26 '18 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.) – Scott Nov 26 '18 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 Nov 26 '18 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='. – Scott Nov 26 '18 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 Nov 26 '18 at 21:42
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.

  • ...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.

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.