【问题标题】:Bash: iteration through array namesBash:遍历数组名称
【发布时间】:2012-07-18 18:53:09
【问题描述】:

我正在尝试编写代码来将一个大数组分解为许多不同的小数组。最终我将通过的数组是一个未知大小的数组,这只是我的测试对象。我已经做到了这一点:

#!/bin/bash
num=(10 3 12 3 4 4)
inArray=${#num[@]}
numArrays=$(($inArray/2))
remain=$(($inArray%2))
echo $numArrays
echo $remain
nun=0
if test $remain -gt  $nun; then
        numArrays=$(($numArrays+1))
fi
array=(1 2)
j=0
for ((i=0;i<$numArrays;i++, j=j+2)); do
        array=("${num[@]:$j:2}")
        echo "The array says: ${array[@]}"
        echo "The size? ${#array[@]}"
done    

我真正遇到的问题是:我想让变量“数组”每次都能稍微更改名称,因此每个数组都被保留并在循环后具有唯一的名称。我曾尝试将名称命名为 array_$i 但返回:

[Stephanie@~]$ ./tmp.sh 
3
0
./tmp.sh: line 16: syntax error near unexpected token `"${num[@]:$j:2}"'
./tmp.sh: line 16: `    array_$i=("${num[@]:$j:2}")'
[Stephanie@RDT00069 ~]$ ./tmp.sh 
3
0
./tmp.sh: line 16: syntax error near unexpected token `$i'
./tmp.sh: line 16: `    array($i)=("${num[@]:$j:2}")'

有人有什么建议吗? 谢谢

【问题讨论】:

  • 我认为eval 可能是您想要的:如eval "array_$i=(\"${num[@]:$j:2}\")"。不过,不确定那里的引用是否正确-您可能需要稍微摆弄一下。
  • 顺便说一句,if test $remain… 块可以写成(( numArrays+=(remain&gt;0?1:0) ))(( numArrays+=(remain&gt;0) ))
  • @kojiro 谢谢!我对 bash 还很陌生,所以很高兴看到如何更雄辩地使用代码

标签: arrays bash loops iteration


【解决方案1】:

我不认为你真的可以在这里避免eval,但如果你小心点,你也许可以安全地做到这一点。这是我的方法:

for name in "${!array_*}"; do # Get all names starting with array_
    i="${name#array_*}" # Get the part after array_
    if [[ $i != *[^0-9]* ]]; then # Check that it's a number.
        printf '%s is not a valid subarray name\n' "$name"
    else
        # Create a variable named "statement" that contains code you want to eval.
        printf -v statement 'cur_array=( "${%s[@]}" )' "$name"
        eval "$statement"
        # Do interesting things with $cur_array
    fi
done

在此之前,当您刚刚创建数组时,您知道$name 应该是什么,所以只需使用printf -v 部分。

为了更安全,您可以将所有允许的数组名称保存在另一个数组和 check that $name is a member 中。

【讨论】:

  • 出于好奇,为什么在这里使用 eval 很危险?
  • @Stephopolis eval 直接执行代码,因此可能会导致难以调试的错误和安全漏洞。用户提供的数据尤其危险。您可以通过验证输入来减轻其中一些风险,但尝试避免 eval 几乎总是有用的。 stackoverflow.com/questions/86513
【解决方案2】:

对于简单的变量,可以使用declare关键字进行间接赋值:

v=foo
declare $v=5
echo $foo    # Prints 5

这并没有扩展到明显(对我来说,无论如何)意义上的数组:

i=2
# This produces a syntax error
declare -a array_$i=("${num[@]:$j:2}")

相反,您可以声明一个空数组

declare -a array_$i

或一次分配一个项目:

declare -a array_$i[0]=item1 array_$i[1]=item2

这是一个使用 for 循环复制第三个的示例 和一个大数组的第 4 个字母变成一个较小的。我们用 i 作为较小数组名称的动态部分,并且 j 作为该数组的索引。

letters=(a b c d e f)
i=1
j=0
for letter in "${letters[@]:2:2}"; do
    # E.g., i=0 and j=1 would result in
    #   declare -a array_0[1]=c
    declare -a array_$i[$j]=$letter
    let j+=1
  done
done

echo ${array_1[@]};  # c d

${foo[@]:x:y} 为我们提供来自foo 的元素x, x+1, ..., x+y-1,并且

您可以将整个内容包装在另一个 for 循环中,以实现将 letters 拆分为 3 个较小数组的目标:

 # We'll create array_0, array_1, and array_2
for i in 0 1 2; do 
  # Just like our subset above, but start at position i*2 instead of
  # a constant.
  for letter in "${letters[@]:$((i*2)):2}"; do
      declare -a array_$i[$j]=$letter
  done
done

一旦你设法填充了你的三个数组,你如何在没有eval 的情况下访问它们? Bash 有间接访问的语法:

v=foo
foo=5
echo ${!v}   # echoes 5!

感叹号表示将后面的单词用作变量,其值应用作要扩展的参数的名称。知道这一点后,您可能会认为您可以执行以下操作,但您错了。

i=1
v=array_$i   # array_1
echo ${!v[0]}  # array_1[0] is c, so prints c, right? Wrong.

在上面,bash 试图找到一个名为v[0] 的变量并将其展开以获取要展开的参数的名称。我们实际上必须将我们的数组加上它的索引视为一个名称:

i=1
v=array_$i[0]
echo ${!v}    # This does print c

【讨论】:

  • 它似乎仍然不喜欢这个。它像以前一样给出语法错误。
  • 恐怕我对您的解决方案感到困惑。 (我不是最精通 bash,我只使用了大约一个月。)你能解释一下 for 循环是如何工作的吗?它应该完全替换我正在使用的那个还是嵌套在我的里面?
  • 对于declare foo=bar 之类的语句,Bash 在不同版本中的行为不一致。最好将declare 放在一行中,将定义放在另一行中。
  • @kojiro,很高兴知道。我已经通过反复试验来理解“声明”,因为它的文档参差不齐。
【解决方案3】:

这应该可以,但这不是一个好的解决方案,另一种语言可能更好 bash 不支持多维数组

eval array_$i='('"${num[@]:$j:2}"')'

然后,例如

eval 'echo "${array_'$i'[0]}"'

【讨论】:

    猜你喜欢
    • 2017-01-30
    • 1970-01-01
    • 1970-01-01
    • 2022-07-16
    • 1970-01-01
    • 1970-01-01
    • 2014-11-04
    • 1970-01-01
    • 2018-05-16
    相关资源
    最近更新 更多