T. Barusseau
T. Barusseau

Reputation: 335

Check if an associative array exists

Since bash 4.2, the -v conditional expression has been added. It's used to check if a variable is set (has been assigned a value). It's a useful tool when writing scripts that run under set -o nounset, because attempting to use a non-initialized variable causes an error.

I have one issue with it, see the sample in a POSIX bash (set -o posix):

$ declare testvar=0
$ [ -v testvar ] && echo "It works!"
It works!
$ declare -A fizz=( [buzz]=jazz )
$ [ -v fizz[buzz] ] && echo "It works!"
It works!
$ [ -v fizz ] && echo "It doesn't work :("
$ 

As you can see, using -v on a regular variable works as expected. Using it to check the presence of a field within an associative array works as expected as well. However, when checking the presence of the associative array itself, it doesn't work.

Why is that, and is there a workaround?

Upvotes: 8

Views: 273

Answers (2)

F. Hauri  - Give Up GitHub
F. Hauri - Give Up GitHub

Reputation: 70822

Strictly check is varname exist as an associative array:

check4Associative() {
    local foo typ
    read foo typ foo < <(declare -p $1 2>&1)
    [ "$typ" ] && [ -z "${typ//-*A*}" ]
}

You could use as:

if check4Associative myVar; then ... ;fi

Sample tries:

unset fizz
check4Associative fizz && echo yes;echo $?
1

fizz=Blah
check4Associative fizz && echo yes;echo $?
1

declare -A fizz
check4Associative fizz && echo yes;echo $?
yes
0

fizz[foo]=bar
check4Associative fizz && echo yes;echo $?
yes
0

One step further: entry count:

check4AssociativeArgs() {
    local -n var=$1
    local keys=(${!var[@]}) foo typ
    read foo typ foo < <(declare -p $1 2>&1)
    [ "$typ" ] && [ -z "${typ//-*A*}" ] &&
        printf "Name: %s, type: %s, entry count: %d\n" $1 ${typ#-} ${#keys[@]}
}

Then

unset fizz
check4AssociativeArgs fizz ; echo $?
1

declare -A fizz
check4AssociativeArgs fizz
Name: fizz, type: A, entry count: 0

fizz[foo]=bar fizz[bar]=baz
check4AssociativeArgs fizz
Name: fizz, type: A, entry count: 2

Upvotes: 1

Paul Hodges
Paul Hodges

Reputation: 15293

If you want to know if it has any entries, you can check ${#fizz[@]}, but that doesn't tell you if it has been created empty. You can use a case statement or other pattern matching in conjunction with error checking.

tst() { local tst;   
  (( $# )) || return                             # *minimal* error handling...
  if tst="$(declare -p $1 2>&1)"                 # does the subshell error?
  then case "$tst" in                            # if not, check the output
       *\)*) (( $( eval echo \${#$1[@]} ) )) &&  # test number of entries
                echo "Has Args"              ||  # some implementations show '()'
                echo "Empty but defined";;       # some don't; this is for the *do*
       *-A*) echo "Empty but defined";;          # this is for the don't
          *) echo ERROR;;                        # shouldn't get here
       esac
  else echo Undefined                            # if doesn't exist
  fi
}


$: unset fizz
$: tst fizz
Undefined
$: declare -A fizz
$: tst fizz
Empty but defined
$: fizz[buzz]=jazz
$: tst fizz
Has Args

Dunno how useful that will be in your context though. Note that I wrote this one explicitly for associative arrays with minimal error checking not directly related to the point. For any sort of real production use this should be expanded a lot.

I don't like the eval, but my bash version doesn't have namerefs. :o/

Upvotes: 1

Related Questions