4

So I know that you can test for the existence of a regular parameter via indirect expansion by doing something like:

foo=1
bar=foo
(( ${(P)+bar} )) && print "$bar exists"

And I know you can test for the existence of a key inside an associative array by doing something like:

foo=([abc]=1)
(( ${+foo[abc]} )) && print "abc exists"

However I can't figure out how to combine the two and test for the existence of a key inside an associative array via indirect expansion. Is this possible without using eval?

I tried several combinations including the following, and none of them worked:

foo=([abc]=1)
bar=foo
(( ${(P)+bar[abc]} )) && print "$bar has key abc" # Test fails
(( ${(P)+${bar}[abc]} )) && print "$bar has key abc" # Passes for nonexistant keys
(( ${${(P)+bar}[abc]} )) && print "$bar has key abc" # Test fails
(( ${${(P)bar}+[abc]} )) && print "$bar has key abc" # prints "zsh: bad output format specification"

4 Answers4

3

It's been discussed here. To avoid passing values you'll have to use a string with the right format in another parameter expansion (${:-word} that is) which is then expanded by ${(P)+...}:

(( ${(P)+${:-${bar}[abc]}} )) && print OK || print FAIL
don_crissti
  • 79,330
  • 30
  • 216
  • 245
2

I don't know a way using ${+param}, but you can use [[ -v $param ]] instead:

foo=([abc]=1)
bar=foo

[[ -v "$bar""[abc]" ]] && print "$bar has key abc"
# or "$bar"[abc] or $bar''[abc] or $bar'[abc]'
# or any other way to suppress $bar[abc] being interpreted as a value in $bar
GammaFunction
  • 121
  • 1
  • 5
1

I found that building an expression and using eval does the trick

  typeset -A ARRAY_TEST

  ARRAY_TEST[key1]=
  ARRAY_TEST[key2]=value2

  ARRAY_NAME=ARRAY_TEST

  # Note: ${NAME+1} will return 1 if a variable is set, otherwise nothing
  #       for example, ${ARRAY_TEST[key1]+1} will be 1 and
  #       but ${ARRAY_TEST[key3]+1} will be nothing  
  [[ $(eval "echo \${${ARRAY_NAME}[key1]+1}") == 1 ]] && echo has key1 || echo no key1
  [[ $(eval "echo \${${ARRAY_NAME}[key2]+1}") == 1 ]] && echo has key2 || echo no key2
  [[ $(eval "echo \${${ARRAY_NAME}[key3]+1}") == 1 ]] && echo has key3 || echo no key3

# Outputs:
#
#    has key1
#    has key2
#    no key3

I ended up creating a function for this...

function array-has-value() {
  local testVariable=$1
  local keyValue=$2

  [[ $(eval "echo \${${testVariable}[$keyValue]+1}") == 1 ]] && return 0 || return 1
}

The following will produce the same results as above:

array-has-value ARRAY_TEST key1 && echo has key1 || echo no key1
array-has-value ARRAY_TEST key2 && echo has key2 || echo no key2
array-has-value ARRAY_TEST key3 && echo has key3 || echo no key3
user485884
  • 11
  • 1
0

Here is another answer I found that works well:

typeset -A foo=([abc]=def)

has_key() {
   local var="${1}[$2]"
   (( ${(P)+${var}} )) && return 0
   return 1
}
has_key foo abc && print "foo has abc"
has_key foo def || print "foo doesn't have def"
# Outputs:
# foo has abc
# foo doesn't have def