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.