Difference in assignment of x=( $@ ) and x="$@"
#!/bin/bash
if [ $# -gt 0 ]; then
snum=( $@ )
echo $snum
fi
When I run the script like ./testscript.sh 1234 4568
The output of echo command is only 1234
, so I guess I am not building an array of all positional arguments?
#!/bin/bash
if [ $# -gt 0 ]; then
snum="$@"
echo $snum
fi
and run ./testscript.sh 1234 4568
The output is 1234 4568
I am wondering why snum=( $@ )
is only taking the first positional argument?
var=( values )
Is an array variable assignment
var=value
is a scalar variable assignment (and with var="$@"
, var
is assigned the concatenation with space of the positional parameters as one string since its a scalar)
$var
on an array expands to the element of index 0, same as ${var[0]}
, not all the elements of the array for which you need ${var[@]}
, a misdesign copied from the Korn shell.
Also note:
- leaving parameter expansions unquoted in list context is the split+glob operator, you almost never want to do that. Ironically, the only place you put quotes above is where they were not needed.
echo
can’t be used to output arbitrary data inbash
.
Here, you want:
#! /bin/bash -
print_space_separated() {
local IFS=' '
printf '%sn' "$*"
}
if [ "$#" -gt 0 ]; then
snum=( "$@" )
print_space_separated "${snum[@]}"
fi
In zsh
whose array design is closer to that of csh
than of ksh
, you could have done:
#! /bin/zsh -
if (( $# > 0 )) {
snum=( $argv )
print -r -- $snum
}
(the print -r
itself is from ksh)
But with the caveat that even though there’s no split+glob upon parameter expansion in that shell, leaving array expansions unquoted like that still removes empty elements. To preserve them, you need the ksh syntax:
#! /bin/zsh -
if (( $# > 0 )) {
snum=( "$@" )
print -r -- "${snum[@]}"
}
(though the "${snum[@]}"
can be shortened to "$snum[@]"
).
Note that in var="$@"
or var="${array[@]}"
, var
ends up containing a string made up of the positional parameters joined with the first character of $IFS
in zsh
and joined with spaces with bash
. POSIX leaves the behaviour unspecified there, $@
is only meant to be used quoted and in list contexts. To get the positional parameters joined with the first character of $IFS
portably, use "$*"
(in either list or non-list context).
To join elements of an array with arbitrary strings (as opposed to the first character of $IFS
), use the j
parameter expansion flag in zsh
: ${(j[that-string])array}
. bash
has no equivalent. fish
has string join -- that-string $array
.
Use "$@"
(incl. double quotes) in a list context to get a list of positional parameters, individually quoted.
Use "$*"
(incl. double quotes) in a scalar context to get the positional parameters concatenated into a single string with the first character of $IFS
(usually a space) as the delimiter.
Using $@
unquoted (as in your first example) or using "$@"
in a scalar context (as in your second example) rarely makes sense. In the bash
shell, using "$@"
in a scalar context is the same as using "$*"
with the first character of $IFS
set to a space.
When using snum=( "$@" )
, you create the array snum
. If you access the variable as $snum
, you will get the first element of the array. It is, in effect, the same as accessing ${snum[0]}
. Using "${snum[@]}"
gives you a list of the individually quoted elements, in a similar manner as "$@"
does. Using "${snum[*]}"
gives you the equivalent of "$*"
, but for the array snum
.
Assuming you want to create an array, snum
, from the list of positional parameters and then print that array if it’s not empty, you may use
#!/bin/bash
snum=( "$@" )
if [ "${#snum[@]}" -gt 0 ]; then
printf '%sn' "${snum[@]}"
fi
This prints the elements of snum
on separate lines if the script was given arguments.
Example run:
bash-5.1$ ./script 1 2 3 "hello world" 4
1
2
3
hello world
4
Notice that the hello world
argument is kept as a single argument, which would not be the case had you forgotten the quotes around $@
.
To print the list of positional parameters at a single string, delimited by colons. The colons are inserted between the positional parameters by modifying the $IFS
string.
#!/bin/bash
IFS=:
snum="$*"
if [ -n "$snum" ]; then
printf '%sn' "$snum"
fi
The difference here is that snum
is now a single string, not an array of elements. The script outputs the string if it is non-empty, i.e., if the script was given at least one non-empty argument.
Or, modifying our first example only slightly to keep using snum
as an array,
#!/bin/bash
snum=( "$@" )
if [ "${#snum[@]}" -gt 0 ]; then
IFS=:
printf '%sn' "${snum[*]}"
fi
Example run:
bash-5.1$ ./script 1 2 3 "hello world" 4
1:2:3:hello world:4