What is the difference between $@ and $* ?
I think it is easier to understand this issue , in the following manner . Say you have this bash script :
#!/bin/bash
ARG='1 2 3'
for val in "$ARG"
do
echo $val;
done
#test.sh
# First example
If you go and run it , using ./test.sh
, the output will be 1 2 3
.
Now , if you write the same script , without quoting the ARG
variable , as in for val in $ARG
, simply put you are not quoting the ARG
variable value , hence it is as if , the for loop is receiving 1 2 3
, and not "1 2 3"
, as such the result is :
1
2
3
The provided examples can be used to understand how $@
, "$@"
, $*
, "$*"
, work in bash .
$@
and $*
, both expand to the positional arguments passed to the script , for example if a script named test
is run using ./test 1 2 "3 4"
, then it has three positional parameters , which are 1
, 2
, and "3 4"
. Hence $@
and $*
, will expand to a line formed of the three words 1 2 3 4
, which are not quoted .
This means if $@
or $*
are used in a for loop , or with a command such as rm
, the command or the for loop will receive a line formed of words which are not quoted , as in 1 2 3 4
, instead of 1 2 "3 4"
, so further tokenization might happen , as in what was originally "3 4"
, is interpreted as 3 4
, hence as the separate words 3
and 4
.
To illustrate this with an example , imagine having a script of :
#!/bin/bash
for val in $@
do
echo $val
done
#test.sh
And you run it , using the command ./test.sh 1 2 'a b'
, then the output will be :
1
2
a
b
The result is the same if for val in $*
is used .
When $@
is quoted as in "$@"
, each individual positional parameter is quoted , so you can think of it , as quote at each position , so for example if the passed arguments where 1 2 "3 4"
, the value of "$@"
which is viewed by a command or a for loop is "1" "2" "3 4"
.
To illustrate this with an example , imagine having the script :
#!/bin/bash
for val in "$@"
do
echo $val
done
#test.sh
And you run it with the command ./test.sh 1 2 'a b'
, then the output will be :
1
2
a b
When "$*"
is quoted , then all the arguments are quoted into a single word , and they are connected using the first character of the IFS
variable .
If IFS
is unset , as in unset IFS
, then a whitespace is the joining character , as in "$1 $2 .."
, whereas if IFS
has a null value , as in IFS=
, then the arguments are joined without using any separator , as in "$1$2..."
.
Being quoted , this means that no more word breakage occur , when using "$*"
, in a command or in a for loop .
This can be illustrated with an example :
#!/bin/bash
for val in "$*"
do
echo $val
done
#test.sh
When running this script using , ./test.sh 1 2 'a b'
, this will output 1 2 a b
, because for the for loop the value which is being passed is "1 2 a b"
, and not 1 2 a b
.
Concerning zsh
, it acts a little bit differently then bash . First of all , when dereferencing a variable using $
, and if the variable has a textual value , then this variable is not split furthermore on space , unless the SH_WORD_SPLIT
option is set . So it is as if the variable value is quoted .
#!/bin/zsh
ARG='1 2 3'
for val in $ARG
do
echo $val
done
# Will output
# 1 2 3
set -o SH_WORD_SPLIT
for val in $ARG
do
echo $val
done
# Will output
# 1
# 2
# 3
If the variable is an array , and the option KSH_ARRAYS
is not set , then each element in the array , is expanded into a word , otherwise if the KSH_ARRAYS
option is set , then only the first element of the array is expanded to a word . Word splitting is always controlled using the variable SH_WORD_SPLIT
.
#!/bin/zsh
ARG=(1 2 3)
for val in $ARG
do
echo $val
done
# Will output
# 1
# 2
# 3
set -o KSH_ARRAYS
for val in $ARG
do
echo $val
done
# Will output
# 1
In zsh
, the special parameters *
, and @
, are actually arrays , but what is important to understand , is that $*
, and $@
, when being unquoted , they act the same , they simply expand to a line formed of words , where each word is an element in the array . The SH_WORD_SPLIT
option controls the splitting of a word .
#!/bin/zsh
for val in $*
do
echo $val
done
# If run using
# ./test.sh '3 4' 1 2
# , then the output
# will be :
# 3 4
# 1
# 2
# A string formed of three words ,
# is formed from the array ,
# since the SH_WORD_SPLIT
# is not set , no further
# splitting is performed .
set -o KSH_ARRAYS
set -o SH_WORD_SPLIT
# set the KSH_ARRAYS ,
# and the SH_WORD_SPLIT
# options .
for val in $@
do
echo $val
done
# If run using
# ./test.sh '3 4' 1 2
# , then the output
# Will be :
# 3
# 4
# 1
# 2
# The KSH_ARRAYS options
# does not affect $* and $@ .
# A string formed of three words ,
# is created , the SH_WORD_SPLIT
# option is set , this means that
# a word is split on whitespace .
When $*
is quoted as in "$*"
, it evaluates to a quoted word , formed of the concatenation of all the elements in the array , joined by the first character of the IFS
variable if set . If the IFS
variable is not set , as in unset IFS
, then a space character is used for joining the elements of the array . If the IFS
variable is set to a null value , as in IFS=
, then the elements of the array , are concatenated without using any separator .
When $@
is quoted as in "$@"
, then each word of the formed string , acts as being quoted , hence each word of the formed string , is not affected by the setting of SH_WORD_SPLIT
, even when being set to on .
#!/bin/zsh
set -o KSH_ARRAYS
set -o SH_WORD_SPLIT
# Set the KSH_ARRAYS ,
# and the SH_WORD_SPLIT
# options to on .
for val in "$*"
do
echo $val
done
# When run using
# ./test.sh '3 4' 1 2
# , the output
# will be :
# 3 4 1 2
# $* is quoted , hence
# all the elements of
# the array are joined
# into a single quoted
# word '3 4 1 2' .
for val in "$@"
do
echo $val
done
# When run using
# ./test.sh '3 4' 1 2
# , the output Will
# be :
# 3 4
# 1
# 2
# Since $@ is quoted ,
# a string is formed
# from the element of
# the array .
# Each word of this string
# corresponds to an element
# of the array .
# Each word of this
# string is quoted ,
# hence it is not
# affected by the value
# of SH_WORD_SPLIT
Originally published at https://difyel.com on April 25, 2021.