Great Big Dot
2018-11-05 21:42:16 UTC
uname output: Linux ArchBox0 4.18.16-arch1-1-ARCH #1 SMP PREEMPT Sat Oct 20 22:06:45 UTC 2018 x86_64 GNU/Linux
Machine Type: x86_64-unknown-linux-gnu
Bash Version: 4.4
Patch Level: 23
Release Status: release
--text follows this line--
Description:
The parameter expansion "${!var[@]}" expands to the indices of an array
(whether linear or associative). The expansion "${var-string}"
returns "${var}" iff var is set and 'string' otherwise. These two
features do not play well together:
$ declare -a -- array=([0]=hello [1]=world)
$ printf -- '%s\n\n' "${!array[@]-Warning: unset}"
bash: hello world: bad substitution
$ declare -a -- array=([0]='helloworld')
$ printf -- '%s\n\n' "${!array[@]-Warning: unset}"
Warning: unset
$ declare -a -- array=([0]='hello world')
$ printf -- '%s\n\n' "${!array[@]-Warning: unset}"
bash: hello world: bad substitution
$ declare -a -- array=()
$ printf -- '%s\n\n' "${!array[@]-Warning: unset}"
Warning: unset
As you can see, accessing the index list of multiple-element arrays
fails when you append the unset expansion. With single-element
arrays, it fails iff the element in question contains any special
characters or whitespace, and thinks the array is unset otherwise.
(Further testing shows that a value of the empty string also throws
an error.) Finally, empty arrays are also considered unset. (This is
the one thing that is consistent with the rest of bash, since empty
arrays themselves are also considered unset by this expansion; that
is, "${array[@]-unset}" yields 'unset' when array isn't set.)
This pattern of behavior is apparently unaffected by changes to IFS,
using a normal variable as a one-element array, using an unset
variable as a zero-element array, or using an associative instead of
linear array. That last one has an interesting wrinkle, however:
$ declare -A -- assoc=(['k e y']='element')
$ printf -- '%s\n\n' "${!assoc[@]-Warning: unset}"
Warning: unset
$ declare -A -- assoc=(['key']='e l e m e n t')
$ printf -- '%s\n\n' "${!assoc[@]-Warning: unset}"
bash: e l e m e n t: bad substitution
Strangely, whether a single-element array errors (as opposed to
giving the wrong result) is only dependent on the the characters in
the *element*, not the *key*---despite the fact that only the key's
value is being requested!
Repeat-By:
$ declare -a arr_2_=(zero one); printf '%s\n' "${!arr_2_[@]-unset}"
bash: zero one: bad substitution
$ declare -a arr_1a=('z e r o'); printf '%s\n' "${!arr_1a[@]-unset}"
bash: z e r o: bad substitution
$ declare -a arr_1b=('zero'); printf '%s\n; "${!arr_1b[@]-unset}"
unset
Fix:
To avoid this problem, you just need to spend another line or two
writing out the relevant conditional explicitly; for example:
# <command> ... "${!array[@]-<default>}"
if [ -v 'array[@]' ]; then
<command> ... "${!array[@]}" ...
else
<command> ... <default> ...
fi
Note that `test -v 'array[@]'` has the same "feature" that
"${array[@]-default}" does: it treats empty arrays as unset.
Machine Type: x86_64-unknown-linux-gnu
Bash Version: 4.4
Patch Level: 23
Release Status: release
--text follows this line--
Description:
The parameter expansion "${!var[@]}" expands to the indices of an array
(whether linear or associative). The expansion "${var-string}"
returns "${var}" iff var is set and 'string' otherwise. These two
features do not play well together:
$ declare -a -- array=([0]=hello [1]=world)
$ printf -- '%s\n\n' "${!array[@]-Warning: unset}"
bash: hello world: bad substitution
$ declare -a -- array=([0]='helloworld')
$ printf -- '%s\n\n' "${!array[@]-Warning: unset}"
Warning: unset
$ declare -a -- array=([0]='hello world')
$ printf -- '%s\n\n' "${!array[@]-Warning: unset}"
bash: hello world: bad substitution
$ declare -a -- array=()
$ printf -- '%s\n\n' "${!array[@]-Warning: unset}"
Warning: unset
As you can see, accessing the index list of multiple-element arrays
fails when you append the unset expansion. With single-element
arrays, it fails iff the element in question contains any special
characters or whitespace, and thinks the array is unset otherwise.
(Further testing shows that a value of the empty string also throws
an error.) Finally, empty arrays are also considered unset. (This is
the one thing that is consistent with the rest of bash, since empty
arrays themselves are also considered unset by this expansion; that
is, "${array[@]-unset}" yields 'unset' when array isn't set.)
This pattern of behavior is apparently unaffected by changes to IFS,
using a normal variable as a one-element array, using an unset
variable as a zero-element array, or using an associative instead of
linear array. That last one has an interesting wrinkle, however:
$ declare -A -- assoc=(['k e y']='element')
$ printf -- '%s\n\n' "${!assoc[@]-Warning: unset}"
Warning: unset
$ declare -A -- assoc=(['key']='e l e m e n t')
$ printf -- '%s\n\n' "${!assoc[@]-Warning: unset}"
bash: e l e m e n t: bad substitution
Strangely, whether a single-element array errors (as opposed to
giving the wrong result) is only dependent on the the characters in
the *element*, not the *key*---despite the fact that only the key's
value is being requested!
Repeat-By:
$ declare -a arr_2_=(zero one); printf '%s\n' "${!arr_2_[@]-unset}"
bash: zero one: bad substitution
$ declare -a arr_1a=('z e r o'); printf '%s\n' "${!arr_1a[@]-unset}"
bash: z e r o: bad substitution
$ declare -a arr_1b=('zero'); printf '%s\n; "${!arr_1b[@]-unset}"
unset
Fix:
To avoid this problem, you just need to spend another line or two
writing out the relevant conditional explicitly; for example:
# <command> ... "${!array[@]-<default>}"
if [ -v 'array[@]' ]; then
<command> ... "${!array[@]}" ...
else
<command> ... <default> ...
fi
Note that `test -v 'array[@]'` has the same "feature" that
"${array[@]-default}" does: it treats empty arrays as unset.