7

I have read of the special variables used in a script $@ and $*. As far as I understand, the arguments used while executing the script are stored into two special variables, once all arguments into $@ and once all arguments into $*, this way they can be accessed inside the script.

I do not understand why there must be two special variables for the same set of arguments. What is the difference, when is the one and when the other special variable used ?

enter image description here

muru
  • 72,889

7 Answers7

3

The simple primitive explanation is:

  • $* all set arguments is one string (arguments are separated by the first character in $IFS)
  • $@ each argument is different string (arguments are separated by newline)

From man bash:

* Expands to the positional parameters, starting from one.  When the expansion is not within  dou‐
  ble  quotes, each positional parameter expands to a separate word.  In contexts where it is per‐
  formed, those words are subject to further word splitting  and  pathname  expansion.   When  the
  expansion occurs within double quotes, it expands to a single word with the value of each param‐
  eter 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.  If IFS is
  unset, the parameters are separated by spaces.  If IFS is null, the parameters are joined  with‐
  out intervening separators.
@ Expands  to the positional parameters, starting from one.  When the expansion occurs within dou‐
  ble quotes, each parameter expands to a separate word.  That is, "$@" is equivalent to "$1" "$2"
  ...   If  the double-quoted expansion occurs within a word, the expansion of the first parameter
  is joined with the beginning part of the original word, and the expansion of the last  parameter
  is  joined  with  the  last part of the original word.  When there are no positional parameters,
  "$@" and $@ expand to nothing (i.e., they are removed).
mikeserv
  • 58,310
Costas
  • 14,916
1

The difference lies in the way the two variables are expanded.

$@ expands so that each argument is separated by a \0NUL and seen individually. Thus, given foo arg1 arg2 arg3, $@ will become arg1\0arg2\0arg3. In particular, each argument is protected from splitting, so that, if arg1 were foo bar, unless further processing is done, it will be seen as foo bar and not foo and bar separately.

$* expands so that each argument is separated by the first character of IFS, the Internal Field Separator. The default IFS has space as its first character, so the expansion of $* is usually the same as that of $@. However, if I were to set, say, IFS=:, then $* will expand to arg1:arg2:arg3.

The use you put them to depends on the situation. For example, here's a trick to quickly get a sum of many numbers:

sum () (
IFS=+
echo $(( $* ))
)

Then:

$ sum 1 2 3
6

This trick is, of course, very fragile.

mikeserv
  • 58,310
muru
  • 72,889
1

Amongst other things:

  • "$*" expands to "arg1 arg2 arg3 …"
  • "$@" expands to "arg1" "arg2" "arg3" …

Thus "$@" is safer. $* may be older, existing for backwards compatibility.

1

From man bash:

Special Parameters
   The  shell treats several parameters specially.  These parameters may only
   be referenced; assignment to them is not allowed.
   *      Expands to the positional parameters, starting from one.  When  the
          expansion  is  not  within double quotes, each positional parameter
          expands to a separate word.  In contexts  where  it  is  performed,
          those  words  are  subject  to  further word splitting and pathname
          expansion.  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.  If IFS is  unset,  the  parameters  are
          separated  by  spaces.   If  IFS is null, the parameters are joined
          without intervening separators.
   @      Expands to the positional parameters, starting from one.  When  the
          expansion  occurs within double quotes, each parameter expands to a
          separate word.  That is, "$@" is equivalent to "$1"  "$2"  ...   If
          the  double-quoted expansion occurs within a word, the expansion of
          the first parameter is joined with the beginning part of the origi‐
          nal  word,  and  the expansion of the last parameter is joined with
          the last part of the original word.  When there are  no  positional
          parameters, "$@" and $@ expand to nothing (i.e., they are removed).

Compare those four cases, especially with argument containing spaces.

for i in $*; do echo "$i"; done
for i in $@; do echo "$i"; done
for i in "$*"; do echo "$i"; done
for i in "$@"; do echo "$i"; done
michas
  • 21,510
0

This is all about how arguments separators (by default spaces) are handled:

"$@" will expand to a sequence of strings, while "$*" will expand to a single string.

Take the example script "test.sh" below:

#$/bin/sh

for i in "$*"; do
  echo $i
done

echo "===="

for i in "$@"; do
  echo $i
done

When called with 4 args invoked like this: ./test.sh 1 2 3 "4 5", will have the following output:

1 2 3 4 5
====
1
2
3
4 5
Marcelo
  • 3,561
0

Special Parameters $* and $@:

There are special parameters that allow accessing all of the command-line arguments at once. $* and $@ both will act the same unless they are enclosed in double quotes, "".

Both the parameter specifies all command-line arguments but the "$*" special parameter takes the entire list as one argument with spaces between and the "$@" special parameter takes the entire list and separates it into separate arguments.

We can write the shell script to process an unknown number of command-line arguments with either the $* or $@ special parameters:

Pacifist
  • 5,787
0

The special parameters "$*" and "$@" refer to every argument in the shell's argument array each in different ways.

  • "$@" - this parameter is how you refer to the shell array as a list of strings. With this you can expand a faithful reproduction of the argument array for handing off to another command. The expansion of "$@" can also be added to at either end by prefixing or affixing strings.

  • "$*" - this parameter is how you refer to the entire array as a single string concatenated on the first character in the value of the $IFS special shell parameter. With this you can serialize your argument array or otherwise transform it on different delimiters.

Both parameters are POSIX portable, but their attributes are not unique to the argument array. In just about every shell that also implements named arrays - such as bash's ${array[@]} - their functions carry over. So "${array[*]}" has the same ties to $IFS as does "$*" and "${array[@]}" reproduces its constituent strings as faithfully as does "$@".

"$@" and "$*" are initially populated by the shell's (or a function/.dot script's) arguments, but that is not the only way to set them.

set 1 2 3 ''
for n do IFS=$((n-1))
    set "$*" "$@$3"
done
printf '<%s>\n' "$@"

OUTPUT

<102030111213132102030212223232-10203011121313-102030-1-2-3-321>
<102030111213132102030212223232>
<10203011121313>
<102030>
<1>
<2>
<3>
<321102030>
mikeserv
  • 58,310