22

How can I test if an associative array is declared in Bash? I can test for a variable like:

[ -z $FOO ] && echo nope

but I doesn't seem to work for associative arrays:

$ unset FOO
$ declare -A FOO
$ [ -z $FOO ] && echo nope
nope
$ FOO=([1]=foo)
$ [ -z $FOO ] && echo nope
nope
$ echo ${FOO[@]}
foo

EDIT:

Thank you for your answers, both seem to work so I let the speed decide:

$ cat test1.sh
#!/bin/bash
for i in {1..100000}; do
    size=${#array[@]}
    [ "$size" -lt 1 ] && :
done
$ time bash test1.sh #best of five

real    0m1.377s
user    0m1.357s
sys     0m0.020s

and the other:

$ cat test2.sh
#!/bin/bash

for i in {1..100000}; do
    declare -p FOO >/dev/null 2>&1 && :
done
$ time bash test2.sh #again, the best of five

real    0m2.214s
user    0m1.587s
sys     0m0.617s

EDIT 2:

Let's speed compare Chepner's solution against the previous ones:

#!/bin/bash

for i in {1..100000}; do
    [[ -v FOO[@] ]] && :
done
$ time bash test3.sh #again, the best of five

real    0m0.409s
user    0m0.383s
sys     0m0.023s

Well that was fast.

Thanks again, guys.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
James Brown
  • 36,089
  • 7
  • 43
  • 59

4 Answers4

20

In bash 4.2 or later, you can use the -v option:

[[ -v FOO[@] ]] && echo "FOO set"

Note that in any version, using

declare -A FOO

doesn't actually create an associative array immediately; it just sets an attribute on the name FOO which allows you to assign to the name as an associative array. The array itself doesn't exist until the first assignment.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    I am so far unable to find any case in which "FOO[@]" in the above code can produce any different outcome than just "FOO". If the intent was to also detect that FOO is not an array, I don't know of any solution that doesn't require using `declare` (the array size check offered below doesn't help). – Ron Burk May 21 '17 at 05:40
  • 1
    I guess a new enough bash could avoid `declare` by using `${FOO@A}` to get the same information about `FOO`. – Ron Burk May 21 '17 at 06:39
  • 3
    It doesn't work. `$ declare -A FOO; [[ -v FOO[@] ]] && echo "FOO set"` never runs "echo". bash 4.4.x – Kirby Jul 26 '18 at 11:18
  • 1
    @Kirby Because `FOO` *isn't* set; all you've done is added an array attribute to the name `FOO`. It doesn't become set (which is what `-v` tests) until you actually assign a value to the array. – chepner Jul 26 '18 at 12:34
  • @chepner, ok got it. – Kirby Jul 26 '18 at 13:13
  • `declare -A FOO=(); [[ -v FOO[@] ]] && echo "FOO set"` doesn't work even though an empty associative array has been allocated and assigned to foo. – Christopher King Jan 03 '21 at 08:08
  • There's no such thing as an empty array (or an array value in general) in `bash`. The first statement, despite its appearance, simply sets the associative array attribute on the *name* `FOO`. – chepner Jan 03 '21 at 13:25
  • 1
    This doesn't determine whether `FOO` _is_ an associative array -- it would return the same way for a numerically-indexed one as well. – Charles Duffy Apr 01 '21 at 02:42
  • `FOO=1; [[ -v FOO[@] ]] && echo "FOO set"` also returns `FOO set`. – fuujuhi Apr 03 '21 at 15:45
  • Also wanted to add that as a result `FOO=1; [[ -v FOO[@] && -v FOO[KEY] ]] && echo "FOO[KEY] set` will return `FOO[KEY] set` or `unbound variable` with `set -u`. – fuujuhi Apr 03 '21 at 16:19
13

You can use declare -p to check if a variable has been declared:

declare -p FOO >/dev/null 2>&1 && echo "exists" || echo "nope"

And to check specifically associative array:

[[ "$(declare -p FOO 2>/dev/null)" == "declare -A"* ]] &&
   echo "array exists" || echo "nope"
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • 1
    You can't use declare -p to check if a variable has been declared if the variable has not been set. – Jani Nov 15 '16 at 17:48
  • 3
    @Jani not sure what you're stating. The declare will return a non-zero exit status (contrary to what the `help` command claims) if the variable has not been set. @anubhava appears to have a typo in the second example (should be `2>/dev/null` I think). Otherwise, this seems to be the only functioning answer if you want to detect whether the name was declared as an associative array. – Ron Burk May 21 '17 at 05:52
  • @RonBurk: Thanks for your comment. `>/dev/null 2>&1` redirects both stdout and stderr to `/dev/null` thus we can only work on the exit code here. – anubhava May 21 '17 at 17:06
  • @RonBurk The question is how you can check if a variable has been **declared**. If you do `declare -A FOO` **without** setting a value, you can't use `declare -p` to test if `FOO` has been declared. The result is indistinguishable between declared and not declared. – Jani May 22 '17 at 07:32
  • 4
    This solution is working. Thank you! (By the way, "selected" answer doesn't...) – Kirby Jul 26 '18 at 11:21
  • 1
    @Jani just in case, `unset a; declare -A a; declare -p a &> '/dev/null' && echo 'exists';`. – Artfaith Jun 09 '22 at 06:24
  • 1
    @Faither That behaviour was added/fixed in bash 4.4 in 2016. The relevant bash changelog entry says, "The '-p' option to declare and similar builtins will display attributes for named variables even when those variables have not been assigned values (which are technically unset)." :) – Jani Jun 10 '22 at 08:23
6

This is a Community Wiki version of an excellent answer by @user15483624 on a question which is now closed as duplicate. Should that user choose to add their own answer here, this should be deleted in favor of the one with their name on it.


The prior answers on this question should be used only when compatibility with bash 4.x and prior is required. With bash 5.0 and later, an expansion to check variable type is directly available; its use is far more efficient than parsing the output of declare -p, and it avoids some of the unintended side effects of other proposals as well..

The following can be used to test whether a bash variable is an associative array.

[[ ${x@a} = A ]]

${x@a} can be used to test whether it is a variable and an array as well.

$ declare x; echo "${x@a}"

$ declare -a y; echo "${y@a}"
a
$ declare -A z; echo "${z@a}"
A
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 1
    Note that `${x@a}` returns `A` even if `x` is not set. However this allows for building a safe test that some key exists: `[[ ${x@a} = A && -v x[key] ]] && echo "x[key] is set"` (works also under `set -u`). – fuujuhi Apr 03 '21 at 16:22
  • @fuujuhi What Bash version are you referring to? Without declaring `x` or with explicitly unsetting it: `unset x; echo "${x@a}"` I get empty string, so I can not confirm your statement. `bash --version` returns 5.0.17(1)-release here. – Cromax Jul 23 '21 at 05:42
  • @Cromax, if I recall the context in which it was made correctly, the statement from fuujuhi was in reference to the declared-but-unset case, not the both-undeclared-and-unset case. (Also, that's "unset" as in "never assigned a value", not as in "had a value but it was removed with `unset`") – Charles Duffy Jul 23 '21 at 12:24
  • @CharlesDuffy You're right, my bad. I guess I was too hasty. Anyway it seems impossible (except for `declare -p` method) to check if regular (non-array) variable has been declared AND not set. – Cromax Jul 27 '21 at 10:20
  • 1
    Do note that `${z@a}` expands to a **list** of attributes that may also contain other attributes, like `r` for readonly, `x` for export, etc. – Masquue Jul 08 '22 at 07:04
  • Is this better than `[ -v x ]` if I know that `x` is either unset or an array? – x-yuri Oct 25 '22 at 10:36
3

One of the easiest ways is to the check the size of the array:

size=${#array[@]}
[ "$size" -lt 1 ] && echo "array is empty or undeclared"

You can easily test this on the command line:

$ declare -A ar=( [key1]=val1 [key2]=val2 ); echo "szar: ${#ar[@]}"
szar: 2

This method allow you to test whether the array is declared and empty or undeclared altogether. Both the empty array and undeclared array will return 0 size.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • 1
    `set -eu` plus `${#array[@]}` results in the following error `array: unbound variable`. – it3xl Mar 26 '20 at 12:25