有别名

有别名

在支持数组变量的 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)

相关内容