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