计算bash数组中元素的数目,其中数组名称是动态的(即存储在变量中)


11

问题的简要说明:

是否有内置的bash方法来计算bash数组中元素的数量,而该数组的名称是动态的(即存储在变量中),而无需求助于对该数组的完全复制或使用eval

更多信息:

使用bash参数替换,可以执行以下操作:

  • 确定数组的长度:
    myArr=(A B C); echo ${#myArr[@]}
  • 通过名称间接引用变量:(
    NAME=myVar; echo ${!NAME}
    这也适用于数组元素):
    NAME=myArr[1]; echo ${!NAME}

但是,如果数组的名称存储在另一个变量中,那么如何确定数组中元素的数量呢?(可以将其视为上述两个参数替换的组合。)例如:

myArr=(A B C D)
NAME=myArr
# Get the number of elements in the array indirectly referenced by NAME.
count=${#$NAME[@]}  # This syntax is invalid. What is the right way?

以下是所有失败的多次尝试:

  # Setup for following attempts:
  myArr=(A B C D)
  NAME=myArr
  EXPR1=$NAME[@]          # i.e. EXPR1='myArr[@]'
  EXPR2=#$NAME[@]         # i.e. EXPR2='#myArr[@]'

  # Failed attempts to get the lengh of the array indirectly:
  1.  count=${#$NAME[@]}  # ERROR: bash: ...: bad substitution
  2.  count=${#!EXPR1}    # ERROR: bash: !EXPR}: event not found
  3.  count=${#\!EXPR1}   # ERROR: bash: ...: bad substitution
  4.  count=${!#EXPR1}    # ERROR: bash: ...: bad substitution
  5.  count=${!EXPR2}     # Returns NULL

我还尝试了上述方法的其他一些变体,但没有找到任何可行的方法而没有:(A)复制数组或(B)使用eval

工作方法:

有几种解决方法可能不是最佳方法(但如果我错了,请纠正我):

方法1:复制数组

将数组分配给另一个(静态命名的)变量,并获取其中的元素数量。

EXPR=$NAME[@]
arrCopy=( "${!EXPR}" )
count=${#arrCopy}

方法2:使用 eval

EXPR="count=\${#$NAME[@]}"  # i.e. 'count=${myArr[@]}'
eval $EXPR
# Now count is set to the length of the array

摘要:

bash中是否有任何内置方法(即参数替换语法)来间接确定数组的长度?如果没有,最有效的方法是什么?我认为这是eval上面的方法,但是是否存在安全性或性能问题eval


2
啊。嵌套变量。我会重新考虑使用嵌套变量以外的方法。这里的实际问题是什么?
muru

1
这是一个有趣的问题。我唯一要提醒您的是,假设某件事物有或没有性能问题。我在进行非常严格的测试以优化大型bash脚本时发现,一些bash内置函数在性能方面很糟糕,实际上,只需删除大型脚本中的一个启动测试,该测试使用了您可能期望的高效方法,即变量扩展,实际上,单行使整个执行速度降低了大约10%到20%。使用计时器在大循环中测试方法,结果可能会让您感到惊讶。
Lizardx

2
bash 名字引用?declare -n ref=abc; abc=(A B C D); printf '%s\n' "${ref[@]}"
iruvar

@muru-这只是语义,但是术语“嵌套变量”在版本2之前与bash有关。Bash v2添加了“间接变量引用”的语法。我只是问是否有特定的语法来获取间接引用数组的长度。我认为bash的作者如果没有被要求的,有用的技术,就不会去为标量数组实现变量间接寻址而不仅仅是保证立即使用“ Ugh”的hack,尽管我相信这值得商de 。
drwatsoncode 2015年

1
我做了一些基准测试:time bash -c 'a=(1 a +); c=a; for ((i=0;i<100000;i++)); do eval "echo \${#$c[@]}"; done' > /dev/null和类似e=$c[@]; d=("${!e}); echo ${#d[@]}的循环。评估大约花费了复制时间的90%。而且我猜想间隙只会增加数组及其元素的大小。
muru

Answers:


4

您应该在索引评估中处理这些东西。如果将其设为数组,则可以通过间接变量的索引进行间接访问。

a=(abc1 def2 ghi3 jkl4 mno5)
r=('a[c=${#a[@]}]' a\[i] a\[@])
for   i in   0 1 2 3 4 5
do    c=
      printf "<%s>\n" "${!r-${!r[i<c?1:2]}}"
      printf "\n\tindex is $i and count is $c\n\n"
done

<abc1>

    index is 0 and count is 5

<def2>

    index is 1 and count is 5

<ghi3>

    index is 2 and count is 5

<jkl4>

    index is 3 and count is 5

<mno5>

    index is 4 and count is 5

<abc1>
<def2>
<ghi3>
<jkl4>
<mno5>

    index is 5 and count is 5

由于bash的索引是从0开始的,因此数组对象的总数总是比最高设置的索引多1,因此:

c=
echo "${a[c=${#a[@]}]-this index is unset}" "$c"

this index is unset 5

...如果提供的话,参数扩展为默认单词。

如果未提供:

c=
${!r}
echo "$c"

5

...没有伤害。

在循环中,我跟踪一个$index变量,并检查它是否至少与$count 一样大。当它较小时,将$rEference var 扩展到,a[i]因为它是有效索引,但是当它等于或大于I时,则将$ref 扩展到整个$aRray。

这是一个函数:

ref_arr(){
    local    index=-1 count=
    local    ref=(   "$1[ count= \${#$1[@]}  ]"
                     "$1[ index ]"    "$1[ @ ]"
    )  &&    printf  "input array '%s' has '%d' members.\n" \
                     "$1"  "${!ref-${count:?invalid array name: "'$1'"}}"
    while    [ "$((index+=1))" -lt "$count"  ]
    do       printf  "$1[$index]  ==  '%s'\n"  "${!ref[1]}"
    done
}
some_array=(some "dumb
            stuff" 12345\'67890 "" \
          '$(kill my computer)')
ref_arr some_array
ref_arr '$(echo won'\''t work)'

input array 'some_array' has '5' members.
some_array[0]  ==  'some'
some_array[1]  ==  'dumb
                stuff'
some_array[2]  ==  '12345'67890'
some_array[3]  ==  ''
some_array[4]  ==  '$(kill my computer)'
bash: count: invalid array name: '$(echo won't work)'


0

bash 4.3 namerefs是天赐之物。但是,您可以这样做:

$ myArr=(A B C D)
$ NAME=myArr
$ tmp="${NAME}[@]"
$ copy=( "${!tmp}" )
$ echo "${#copy[@]}"
4

感谢您的答复,但是您的答案就是我在“方法1:复制阵列”一节中已经描述的内容。该问题还明确指出,应“不求助于阵列的完整副本”来确定阵列长度,这正是您所做的。
drwatsoncode,2015年
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.