Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Anomalous test -v results with bash associative array

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?

like image 332
flabdablet Avatar asked Aug 19 '26 22:08

flabdablet


2 Answers

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
like image 145
choroba Avatar answered Aug 22 '26 23:08

choroba


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.

like image 39
flabdablet Avatar answered Aug 22 '26 23:08

flabdablet