Posted :
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