Reputation: 4325
It seems [[
uses rather different evaluation rules that plain test
([
) does.
However those rules are not properly described in the bash manual (IMHO).
Consider this example:
> a[1]=''
> i=1
> [[ -v a[i] ]] && echo true || echo false
true
> [[ -v a[$i] ]] && echo true || echo false
true
> [[ -v a[$j] ]] && echo true || echo false
false
> i=0
> [[ -v a[$i] ]] && echo true || echo false
false
> [[ -v ${a[$i]} ]] && echo true || echo false
false
> i=1
> [[ -v ${a[$i]} ]] && echo true || echo false
false
So the question is:
*Where can I leave out the $
, where must I use the $
, and where can I use the $
inside [[
(without affecting the correct result)?
(https://stackoverflow.com/a/74401112/6607497 says (via https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Arrays)):
The subscript is treated as an arithmetic expression that must evaluate to a number
It seems to me that when using associative arrays, then the key must be expanded using a $
.
Consider this set of commands:
> a[k]=k
> declare -p a
declare -a a=([0]="k")
> declare -A A
> A[k]=k
> declare -p A
declare -A A=([k]="k" )
> [[ -v a[k] ]] && echo true || echo false
true
> [[ -v A[k] ]] && echo true || echo false
true
> [[ -v a[j] ]] && echo true || echo false
true
> [[ -v A[j] ]] && echo true || echo false
false
> j=k
> [[ -v a[j] ]] && echo true || echo false
true
> [[ -v A[j] ]] && echo true || echo false
false
> [[ -v A[$j] ]] && echo true || echo false
true
Upvotes: 0
Views: 52
Reputation: 140930
One of the confusing things is that a[i] seems to be treated the same as a[$i], but it seems if i is not a number, then I need the $
From https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Arrays :
The subscript is treated as an arithmetic expression that must evaluate to a number
From https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Arithmetic :
Shell variables are allowed as operands; parameter expansion is performed before the expression is evaluated. Within an expression, shell variables may also be referenced by name without using the parameter expansion syntax. [...] A null value evaluates to 0. [...]
From that:
a[1]=''
i=1
[[ -v a[i] ]] # Arithmetic expression `i` evaluates to 1.
# Element a[1] exists. True.
[[ -v a[$i] ]] # `$i` is expanded -> [[ -v a[1] ]]
# Arithmetic expression `1` evaluates to 1.
# Element a[1] exists. True.
[[ -v a[$j] ]] # `$j` is expanded -> [[ -v a[] ]]
# Arithmetic expression '' evaluates to 0. (a null value evaluates to 0)
# Element a[0] does not exist. False.
i=0
[[ -v a[$i] ]] # Expands to [[ -v a[0] ]]
# False, a[0] does not exist
[[ -v ${a[$i]} ]] # Expands to [[ -v '' ]]
# False, variable with empty name does not exist.
i=1
[[ -v ${a[$i]} ]] # Expands to [[ -v 0 ]]
# False, variable named `0` does not exist.
This is unrelated to [[
. You can do echo "${a[i]}" "${a[i + 1]}"
or echo "$(( ))"
. What [[
does is disables word splitting and filename expansion on the words that are it's arguments.
Note that for example, in the first case, the [[
builtin receives 4 arguments -v
a[i]
]]
. i
is not expanded. Then, [[
builtin internally handles the a[i]
expression. In that sense, [[ -v
is super special, where the "expansion" of the expression i
inside a[i]
"happens" inside the command after the builtin was called.
Where can I leave out the $, where must I use the $, and where can I use the $ inside [[ (without affecting the correct result)?
Note that $(( $i ))
is effectively double expanding:
a=123
b=a
$(( $b )) # expands to $(( a ))
# evaluates `a` to 123
You usually want to leave out $
inside arithmetic expression. Also, there are associative arrays, for which a[$i]
and a[i]
has different behavior, as the part of [<here>]
is no longer an arithmetic expression. So where you "can" leave out depends on the behavior you want to get. Working with indexed arrays, you want to just a[i]
.
Upvotes: 3