Bash empty array expansion with `set -u`
@ikegami's accepted answer is subtly wrong! The correct incantation is ${arr[@]+"${arr[@]}"}
:
$ countArgs () { echo "$#"; }
$ arr=('')
$ countArgs "${arr[@]:+${arr[@]}}"
0 # WRONG
$ countArgs ${arr[@]+"${arr[@]}"}
1 # RIGHT
$ arr=()
$ countArgs ${arr[@]+"${arr[@]}"}
0 # Let's make sure it still works for the other case...
According to the documentation,
An array variable is considered set if a subscript has been assigned a value. The null string is a valid value.
No subscript has been assigned a value, so the array isn't set.
But while the documentation suggests an error is appropriate here, this is no longer the case since 4.4.
$ bash --version | head -n 1
GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)
$ set -u
$ arr=()
$ echo "foo: '${arr[@]}'"
foo: ''
There is a conditional you can use inline to achieve what you want in older versions: Use ${arr[@]+"${arr[@]}"}
instead of "${arr[@]}"
.
$ function args { perl -E'say 0+@ARGV; say "$_: $ARGV[$_]" for 0..$#ARGV' -- "$@" ; }
$ set -u
$ arr=()
$ args "${arr[@]}"
-bash: arr[@]: unbound variable
$ args ${arr[@]+"${arr[@]}"}
0
$ arr=("")
$ args ${arr[@]+"${arr[@]}"}
1
0:
$ arr=(a b c)
$ args ${arr[@]+"${arr[@]}"}
3
0: a
1: b
2: c
Tested with bash 4.2.25 and 4.3.11.
The only safe idiom is ${arr[@]+"${arr[@]}"}
Unless you only care about Bash 4.4+, but you wouldn't be looking at this question if that were the case :)
This is already the recommendation in ikegami's answer, but there's a lot of misinformation and guesswork in this thread. Other patterns, such as ${arr[@]-}
or ${arr[@]:0}
, are not safe across all major versions of Bash.
As the table below shows, the only expansion that is reliable across all modern-ish Bash versions is ${arr[@]+"${arr[@]}"}
(column +"
). Of note, several other expansions fail in Bash 4.2, including (unfortunately) the shorter ${arr[@]:0}
idiom, which doesn't just produce an incorrect result but actually fails. If you need to support versions prior to 4.4, and in particular 4.2, this is the only working idiom.
Unfortunately other +
expansions that, at a glance, look the same do indeed emit different behavior. Using :+
instead of +
(:+"
in the table), for example, does not work because :
-expansion treats an array with a single empty element (('')
) as "null" and thus doesn't (consistently) expand to the same result.
Quoting the full expansion instead of the nested array ("${arr[@]+${arr[@]}}"
, "+
in the table), which I would have expected to be roughly equivalent, is similarly unsafe in 4.2.
You can see the code that generated this data along with results for several additional version of bash in this gist.