检查变量是像Bourne这样的shell中的数组吗?


14

在像shell这样的Bourne中,它支持数组变量,我们可以使用一些解析来检查变量是否为数组。

下面的所有命令都在运行后运行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内建像=~[[ ... ]]不需要子shell,但仍然过于复杂。

有没有更简单的方法来完成此任务?


在什么情况下,您需要检查变量是否为数组?
库萨兰达

Answers:


10

我不认为您可以,而且我认为这实际上没有任何区别。

unset a
a=x
echo "${a[0]-not array}"

x

ksh93和中的任何一个都执行相同的操作bash。看起来所有变量可能都是这些shell中的数组,或者至少是没有分配特殊属性的任何常规变量,但是我没有检查太多。

bash手册讨论了使用+=赋值时数组与字符串变量的不同行为,但此后进行了套期并指出该数组仅在复合赋值上下文中表现不同。

它还指出,如果已为任何下标分配了值,则该变量将被视为数组-并明确包括存在空字符串的可能性。在上面可以看到,常规分配肯定会导致分配下标-因此,我想一切都是数组。

实际上,可能您可以使用:

[ 1 = "${a[0]+${#a[@]}}" ] && echo not array

...以清楚地查明仅被赋值为0的单个下标的设置变量。


所以我想检查一下是否${a[1]-not array}可以完成任务,不是吗?
cuonglm

@cuonglm-好吧,根据bash手册的规定:如果为下标分配了值,则认为已设置数组变量。空字符串是有效值。如果有任何下标被分配,则其每个规范都为一个数组。实际上,也没有,因为您可以做到a[5]=x。我想[ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ]可以。
mikeserv

6

因此,您实际上想要的只是中间部分而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

@mikeserv好点。别名使它看起来更漂亮。+1
PSkocik 2015年

我的意思是alias vartype="$VARTYPE"-...或根本没有定义$VARTYPE-它应该起作用,对吧?您只需要添加该shopt内容,bash因为它违反了有关alias脚本扩展的规范。
mikeserv

1
@mikeserv我确定cuonglm很有能力根据他的需求和喜好调整这种方法。;-)
PSkocik

...和安全考虑。
PSkocik

上面的代码根本不会评估用户提供的文本。它的安全性不亚于功能。我从未见过您对将函数设置为只读而大惊小怪,但可以,我可以将变量标记为只读。
PSkocik 2015年

6

在zsh中

zsh% a=(1 2 3) s=1
zsh% [[ ${(t)a} == *array* ]] && echo array
array
zsh% [[ ${(t)s} == *array* ]] && echo array
zsh%

也许echo ${(t)var}更简单。谢谢你

4

要测试变量var,请使用

b=("${!var[@]}")
c="${#b[@]}"

可以测试是否有多个数组索引:

[[ $c > 1 ]] && echo "Var is an array"

如果第一个索引值不为零:

[[ ${b[0]} -eq 0 ]] && echo "Var is an array"      ## should be 1 for zsh.

唯一的难题是只有一个索引值且该值为零(或一个)时。

对于这种情况,可以使用一种副作用,尝试从非数组变量中删除数组元素:

**bash** reports an error with             unset var[0]
bash: unset: var: not an array variable

**zsh** also reports an error with         $ var[1]=()
attempt to assign array value to non-array

这适用于bash:

# Test if the value at index 0 could be unset.
# If it fails, the variable is not an array.
( unset "var[0]" 2>/dev/null; ) && echo "var is an array."

对于zsh,索引可能需要为1(除非兼容模式处于活动状态)。

需要子外壳以避免擦除var的索引0的副作用。

我发现没有办法使其在ksh中工作。

编辑1

此功能仅在bash4.2 +中有效

getVarType(){
    varname=$1;
    case "$(typeset -p "$varname")" in
        "declare -a"*|"typeset -a"*)    echo array; ;;
        "declare -A"*|"typeset -A"*)    echo hash; ;;
        "declare -- "*|"typeset "$varname*| $varname=*) echo scalar; ;;
    esac;
}

var=( foo bar );  getVarType var

编辑2

这也仅适用于bash4.2 +

{ typeset -p var | grep -qP '(declare|typeset) -a'; } && echo "var is an array"

注意:如果var包含测试过的字符串,这将产生误报。


零元素数组怎么样?
cuonglm

1
日期编辑,寿。看起来很原始。:D
PSkocik

@cuonglm ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."如果将var设置为var=()具有零个元素的数组,则检查会正确报告var是一个数组。它的行为与声明完全相同。

如果导出了标量或将其标为整数/小写/只读,则对标量的测试将无法进行。您可以放心地确定,任何其他非空输出都意味着标量变量。我会使用grep -E而不是grep -P避免对GNU grep的依赖。
斯特凡Chazelas

@StéphaneChazelas对整数和/或小写和/或只读的标量的测试(以bash表示)始终以开头-a,如下所示:declare -airl var='()'。因此,grep测试将起作用

3

对于bash来说,这有点hack(尽管有记录):尝试typeset用于删除“ array”属性:

$ 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
}

请注意使用typeset -g(bash-4.2或更高版本),这在函数中是必需的,因此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确实没有硬性区别,其中某些(如果您检查Changelog)(可以检查klog)可以归因于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 )

这也需要一个子shell。

一种更有用的技术是compgen

compgen -A arrayvar

这将列出所有索引数组,但是关联数组未经过特殊处理(最高bash-4.4),并显示为常规变量(compgen -A variable


typeset +a还报告KSH错误。但是,不在zsh中。

1

简短答案:

对于引入该符号(bashksh93)的两个外壳,标量变量只是一个具有一个元素的数组

两者都不需要特殊的声明来创建数组。只需分配就足够了,简单分配var=value就等于var[0]=value


试试:bash -c 'unset var; var=foo; typeset -p var'。bash答案是否报告数组(需要-a)?现在比较:bash -c 'unset var; var[12]=foo; typeset -p var'。为什么有区别?答:shell维护(无论好坏)一个变量是标量或数组的概念。shell ksh确实将这两个概念混为一谈。

1

yash的array内置函数有一些仅适用于数组变量的选项。示例:该-d选项将报告有关非数组变量的错误:

$ a=123
$ array -d a
array: no such array $a

因此,我们可以执行以下操作:

is_array() (
  array -d -- "$1"
) >/dev/null 2>&1

a=(1 2 3)
if is_array a; then
  echo array
fi

b=123
if ! is_array b; then
  echo not array
fi

如果数组变量为readonly,则此方法将不起作用。尝试修改只读变量导致错误:

$ a=()
$ readonly a
$ array -d a
array: $a is read-only

0
#!/bin/bash

var=BASH_SOURCE

[[ "$(declare -pa)" =~ [^[:alpha:]]$var= ]]

case "$?" in 
  0)
      echo "$var is an array variable"
      ;;
  1)
      echo "$var is not an array variable"
      ;;
  *)
      echo "Unknown exit code"
      ;;
esac
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.