Ran across this apparent anomaly. Test code:
(
set -x
declare -A a=()
for i in x "'" '"'
do
a[$i]=
test -v a[$i] && true
done
: "${!a[@]}"
)
Output:
+ a=()
+ declare -A a
+ for i in x "'" '"'
+ a[$i]=
+ test -v 'a[x]'
+ true
+ for i in x "'" '"'
+ a[$i]=
+ test -v 'a['\'']'
+ for i in x "'" '"'
+ a[$i]=
+ test -v 'a["]'
+ : \' '"' x
I would have expected true to be executed three times, not just the once. It seems that test -v fails for associative array elements where the index expands into something containing a quote character.
Is this a bash bug, or just me needing better-informed expectations?
Seems to be a bug.
I've found a workaround, though:
declare -A a=()
for i in x '"a"' a '"a"' ']' '[' '\' "'" '"' y
do
a[$i]=
test -v 'a[$i]' && true
done
OK, sussed it.
This works:
(
set -x
declare -A a=()
a[x]= a["'"]= a['"']=
for i in x "'" '"' y
do
test -v 'a[$i]' && true
done
: "${!a[@]}"
)
Output:
+ a=()
+ declare -A a
+ a[x]=
+ a["'"]=
+ a['"']=
+ for i in x "'" '"' y
+ test -v 'a[$i]'
+ true
+ for i in x "'" '"' y
+ test -v 'a[$i]'
+ true
+ for i in x "'" '"' y
+ test -v 'a[$i]'
+ true
+ for i in x "'" '"' y
+ test -v 'a[$i]'
+ : \' '"' x
Now, the only test -v that fails is the one for the array element a[y] which was indeed never set.
The unwritten rule appears to be that the name argument supplied to test -v name gets dealt with in exactly the same way as whatever can go on the left hand side of the = in a shell variable assignment as the shell parses command lines. Specifically, any expansion syntax inside the subscript portion of any such name will be evaluated before testing whether the resulting name has a referent. Furthermore, to make quote removal operate correctly, such expansions have to be deferred in that way; they can't be allowed to happen before name gets passed as an argument to test.
Same appears to apply to names supplied as arguments to unset.
Closest I can find to this in the bash manual is this paragraph in the section on arrays:
When using a variable name with a subscript as an argument to a command, such as with unset, without using the word expansion syntax described above, the argument is subject to the shell’s filename expansion. If filename expansion is not desired, the argument should be quoted.
which strikes me as kind of tangential to the issue at hand. And I don't think it's just variable expansion: I think it goes the full Monty. Try unset 'a[x$(ls -al >&2)]' if you feel like having a bit of a shudder; might be a fun little class of script injection vulnerabilities lurking in there.
Summarizing: if you're going to use test -v to test for the existence of an entry in an associative array, wrap the name in single quotes, not double quotes, even if it includes expansions of other variables. You want test -v 'a[$i]', not anything that expands the same way as test -v "a[$i]" would. Same applies to unset.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With