For bash, it's a little bit of a hack (albeit documented): attempt to use typeset to remove the "array" attribute:
$ typeset +a BASH_VERSINFO
bash: typeset: BASH_VERSINFO: cannot destroy array variables in this way
echo $?
1
(You cannot do this in zsh, it allows you to convert an array to a scalar, in bash it's explicitly forbidden.)
So:
typeset +A myvariable 2>/dev/null || echo is assoc-array
typeset +a myvariable 2>/dev/null || echo is array
Or in a function, noting the caveats at the end:
function typeof() {
local _myvar="$1"
if ! typeset -p $_myvar 2>/dev/null ; then
echo no-such
elif ! typeset -g +A $_myvar 2>/dev/null ; then
echo is-assoc-array
elif ! typeset -g +a $_myvar 2>/dev/null; then
echo is-array
else
echo scalar
fi
}
Note the use of typeset -g (bash-4.2 or later), this is required within a function so that typeset (syn. declare) doesn't work like local and clobber the value you are trying to inspect. This also does not handle function "variable" types, you can add another branch test using typeset -f if needed.
Another (nearly complete) option is to use this:
${!name[*]}
If name is an array variable, expands to the list
of array indices (keys) assigned in name. If name
is not an array, expands to 0 if name is set and
null otherwise. When @ is used and the expansion
appears within double quotes, each key expands to a
separate word.
There's one slight problem though, an array with a single subscript of 0 matches two of the above conditions. This is something that mikeserv also references, bash really doesn't have a hard distinction, and some of this (if you check the Changelog) can be blamed on ksh and compatibilty with how ${name[*]} or ${name[@]} behave on a non-array.
So a partial solution is:
if [[ ${!BASH_VERSINFO[*]} == '' ]]; then
echo no-such
elif [[ ${!BASH_VERSINFO[*]} == '0' ]]; then
echo not-array
elif [[ ${!BASH_VERSINFO[*]} != '0' ]];
echo is-array
fi
I have used in the past a variation on this:
while read _line; do
if [[ $_line =~ ^"declare -a" ]]; then
...
fi
done < <( declare -p )
this too needs a subshell though.
One more possibly useful technique is compgen:
compgen -A arrayvar
This will list all indexed arrays, however associative arrays are not handled specially (up to bash-4.4) and appear as regular variables (compgen -A variable)