在支持数组变量的 Bourne 之类的 shell 中,我们可以使用一些解析来检查变量是否是数组。
下面的所有命令都是在运行后运行的a=(1 2 3)
。
zsh
:
$ declare -p a
typeset -a a
a=( 1 2 3 )
bash
:
$ declare -p a
declare -a a='([0]="1" [1]="2" [2]="3")'
ksh93
:
$ typeset -p a
typeset -a a=(1 2 3)
pdksh
及其导数:
$ typeset -p a
set -A a
typeset a[0]=1
typeset a[1]=2
typeset a[2]=3
yash
:
$ typeset -p a
a=('1' '2' '3')
typeset a
中的一个例子bash
:
if declare -p var 2>/dev/null | grep -q 'declare -a'; then
echo array variable
fi
这种方法工作量太大,需要生成一个子 shell。使用其他 shell 内置命令(如=~
in)[[ ... ]]
不需要子 shell,但仍然太复杂。
有没有更简单的方法来完成这项任务?
答案1
我不认为你可以,而且我认为这实际上没有任何区别。
unset a
a=x
echo "${a[0]-not array}"
x
ksh93
这在和中执行相同的操作bash
。看起来有可能全部变量是这些 shell 中的数组,或者至少是任何尚未分配特殊属性的常规变量,但我没有检查太多。
该bash
手册讨论了使用赋值时数组与字符串变量的不同行为+=
,但随后它对冲并指出数组仅在以下情况下表现不同:化合物分配上下文。
它还指出,如果为任何下标分配了值,则变量被视为数组 - 并且明确包含空字符串的可能性。在上面你可以看到,常规赋值肯定会导致分配下标 - 所以我猜一切都是一个数组。
实际上,您可能可以使用:
[ 1 = "${a[0]+${#a[@]}}" ] && echo not array
...清楚地查明仅分配了值 0 的单个下标的集合变量。
答案2
所以你实际上只想要中间部分而declare -p
不是周围的垃圾?
您可以编写一个宏,例如:
readonly VARTYPE='{ read __;
case "`declare -p "$__"`" in
"declare -a"*) echo array;;
"declare -A"*) echo hash;;
"declare -- "*) echo scalar;;
esac;
} <<<'
这样你就可以:
a=scalar
b=( array )
declare -A c; c[hashKey]=hashValue;
######################################
eval "$VARTYPE" a #scalar
eval "$VARTYPE" b #array
eval "$VARTYPE" c #hash
(如果您想在函数局部变量上使用它,那么仅仅一个函数是不行的)。
有别名
shopt -s expand_aliases
alias vartype='eval "$VARTYPE"'
vartype a #scalar
vartype b #array
vartype c #hash
答案3
在zsh中
zsh% a=(1 2 3) s=1
zsh% [[ ${(t)a} == *array* ]] && echo array
array
zsh% [[ ${(t)s} == *array* ]] && echo array
zsh%
答案4
为了巴什,这有点像黑客(尽管有记录):尝试使用typeset
删除“数组”属性:
$ typeset +a BASH_VERSINFO
bash: typeset: BASH_VERSINFO: cannot destroy array variables in this way
echo $?
1
(您不能在 中执行此操作zsh
,它允许您将数组转换为标量,在bash
中明确禁止此操作。)
所以:
typeset +A myvariable 2>/dev/null || echo is assoc-array
typeset +a myvariable 2>/dev/null || echo is array
或者在函数中,注意末尾的警告:
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
}
请注意 (bash-4.2 或更高版本) 的使用typeset -g
,这是函数中必需的,以便typeset
(syn. declare
) 不会像local
您尝试检查的值那样工作并破坏您要检查的值。这也不处理函数“变量”类型,您可以根据typeset -f
需要添加另一个分支测试。
另一个(几乎完整的)选项是使用这个:
${!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.
但有一个小问题,单个下标为 0 的数组符合上述两个条件。 mikeserv 也引用了这一点,bash 确实没有严格的区别,其中一些(如果您检查更改日志)可以归咎于 ksh 以及与非数组上的行为方式${name[*]}
的兼容性。${name[@]}
所以一个部分的解决方案是:
if [[ ${!BASH_VERSINFO[*]} == '' ]]; then
echo no-such
elif [[ ${!BASH_VERSINFO[*]} == '0' ]]; then
echo not-array
elif [[ ${!BASH_VERSINFO[*]} != '0' ]];
echo is-array
fi
我过去曾使用过此方法的变体:
while read _line; do
if [[ $_line =~ ^"declare -a" ]]; then
...
fi
done < <( declare -p )
不过,这也需要一个子外壳。
另一种可能有用的技术是compgen
:
compgen -A arrayvar
这将列出所有索引数组,但是关联数组不会被特殊处理(直到 bash-4.4)并显示为常规变量 ( compgen -A variable
)