I seem to remember some discussion of this before -- not being able to use -v to check if a hash was defined, but having it "work"(?) w/arrays?
I think, though, the fact that it operates inconsistently makes it a bug. I.e. if always work or fail, at least it is behaving consistently, feels wrong. The rule for whether it works or not is whether or not the there is a value for '[0]' -- whether it is a hash or an array. I.e:
hash hash_cfg=() dummy_hash array array_cfg=() dummy_array
# define exists func to make life easier...
exists () { see below...; } type exists
exists () { (($#)) || { printf '"Null" is not set (no param)'; return 1 }; printf 'var "%s" is' "$1"; [[ -v $1 ]] || printf " not"; printf ' set\n' } # 1st check what bash "thinks":
my -p hash_cfg array_cfg dummy hash dummy array
declare -A hash_cfg='()' declare -a array_cfg='()' -bash: declare: dummy_hash: not found -bash: declare: dummy_array: not found # bash thinks only the assigned-to vars "exist" - "normal" # assign some values:
array_cfg+=([1]=one) hash_cfg+=([one]=1) exists hash_cfg
var "hash_cfg" is not set
exists array_cfg
var "array_cfg" is not set # "-v" doesn't think either is set # but bash shows "set" content in each:
my -p hash_cfg array_cfg
declare -A hash_cfg='([one]="1" )' declare -a array_cfg='([1]="one")' # assign some more values
hash_cfg+=([zero]=0) array_cfg+=([0]=zero) exists hash_cfg; exists array_cg
var "hash_cfg" is not set var "array_cfg" is set # Now "-v" thinks the array is set, but not the hash So for hashes and arrays, "-v" only looks at the [0] member, which is more than a bit wrong... it would be like some "newbie" thinking they could list the contents of an array just by using "echo $NAME" , which, for an array lists the "first" element[sic], but **NOT** for a hash:
echo "h=$hash_cfg, a=$array_cfg"
h=, a=zero # So lets give the hash a k,v pair of 0,zero:
hash_cfg+=([0]=zero) exists hash_cfg
var "hash_cfg" is set # how does echo work now?
echo "h=$hash_cfg, a=$array_cfg"
h=zero, a=zero # note that printing w/bash shows:
my -p hash_cfg array_cfg
declare -A hash_cfg='([one]="1" [zero]="0" [0]="zero" )' declare -a array_cfg='([0]="zero" [1]="one")' i.e. if there is a _key_ or index of 0, it is printed, when no subscript is given, whereas the manpage says: Referencing an array variable without a subscript is equivalent to referencing the array with a subscript of 0. It probably should say this is true for hashes as well (since it is -- ;-). Additionally, the manpage says: An array variable is considered set if a subscript has been assigned a value. The null string is a valid value. But this isn't exactly true either:
array new_ar=([1]=one) exists new_ar
var "new_ar" is not set
echo "new_ar=$new_ar"
new_ar= Only the assignment to element zero qualifies it as existing w/-v, and the same holds true for a hash. # Note, "-v" knows that other members in the hash and array # are set, if it is asked about them:
exists hash_cfg[one]; exists array_cfg[1]
var "hash_cfg[one]" is set var "array_cfg[1]" is set ---- At the very least, the manpage should probably be updated to talk about hash's. Ideally, "-v" would be fixed to look at #members of an array or hash and if >0, use that as "defined" else not. Would that be doable & not too difficult? I enclosed a sample alpha-level function to sorta show what I mean. It needs to be 'sourced' to work properly, then call it as: exists VARNAME I don't see any possible compat probs -- only from the fact that as it is now, it's not really usable to give useful information about arrays and hashes...whereas returning set only if the data object has members in it would seem to be the most the closest parallel definition -- as for vars -- it is supposed to return whether or not they are defined and have been assigned to. If people really felt strongly about it, though, I wouldn't be totally adverse if it return "true" if it had been assigned an empty list as well (i.e. array a=(); hash=h(); -- has those will show up as existing w/dcl's "-p". Ooops... just noticed that it doesn't correctly report on existence of functions...sigh... well later on that one! ;-)
#!/bin/bash -u # "proof-of-concept", "alpha level quality" # -LAWalsh, 2016 export PS4='>${BASH_SOURCE:+${BASH_SOURCE[0]/$HOME/\~}}#${LINENO}${FUNCNAME:+(${FUNCNAME[0]})}> ' shopt -s expand_aliases alias my=declare sub=function alias int='my -i' array='my -a' map='my -A' sub _typ () { my tp_fld="" jnk="" read -r jnk tp_fld jnk< <( my -p $1 2>/dev/null || { array fdef=() readarray fdef< <(my -pf $1 2>/dev/null) if [[ ${#fdef[@]} && ${fdef[0-1]} =~ ^declare ]]; then printf "%s" ${fdef[0-1]} else echo ""; fi; } ) if [[ ${#tp_fld} == 0 ]]; then echo "" return 0 fi array ops=( $(echo "$tp_fld"|sed -r 's/-//g; s/(.)/\1 /g') ) my md="" o="" sep="" vt='' int dset=0 for o in "${ops[@]}"; do ((${#o})) || continue if [[ ! $vt && ${types[$o]:-""} ]]; then vt=${types[$o]} fi if [[ ! ${mods[$o]:=""} && ${mods[$o]} != ${vt:-} ]]; then md+="$sep${mods[$o]}"; sep=" " fi done : ${vt:=var} printf "%s%s\n" "${md:+"$md "}" $vt } sub exists () { map mods=(['i']=int ['l']=lower ['u']=upper ['x']=exported ['r']=readonly) map types=(['a']=array ['A']=hash ['i']=int ) (($#)) || { printf '"Null" is not set (no param)'; return 1 } my name=$1 my set=0 my tp=$(_typ "$name") if [[ $tp && $tp == array || $tp == hash ]];then set=$(eval "echo \${#$name[@]}") else [[ -v $name ]] && set=1; fi printf '%s "%s" is' "$tp" "$name"; ((set)) || printf " not"; printf ' set\n' } shopt -s extdebug [[ $0 =~ bash ]] && (($#)) && { exists "$1" }