如何使用间接引用迭代数组?

时间:2012-06-24 19:49:52

标签: bash scripting indirection

如何使此代码有效?

#!/bin/bash
ARRAYNAME='FRUITS'
FRUITS=( APPLE BANANA ORANGE )
for FRUIT in ${!ARRAYNAME[@]}
do
    echo ${FRUIT}
done

此代码:

echo ${!ARRAYNAME[0]}

打印 APPLE 。我尝试做类似的事情,但用“[@]”迭代数组。

提前致谢,

6 个答案:

答案 0 :(得分:26)

${!ARRAYNAME[@]}表示“ARRAYNAME”的索引。正如设置ARRAYNAMEbash man page中所述,但作为字符串而不是数组,它返回0

以下是使用eval的解决方案。

#!/usr/bin/env bash

ARRAYNAME='FRUITS'
FRUITS=( APPLE BANANA ORANGE )

eval array=\( \${${ARRAYNAME}[@]} \)

for fruit in "${array[@]}"; do
  echo ${fruit}
done

您最初尝试做的是创建Indirect Reference。这些是在bash版本2中引入的,当试图在shell中实现类似反射的行为时,它们在很大程度上取代了对eval的需求。

在对数组使用间接引用时,您需要做的是在猜测变量名时包含[@]

#!/usr/bin/env bash

ARRAYNAME='FRUITS'
FRUITS=( APPLE BANANA ORANGE )

array="${ARRAYNAME}[@]"
for fruit in "${!array}"; do
  echo $fruit
done

所有这一切,在这个简单的例子中使用间接引用是一回事,但是,正如Dennis Williamson提供的链接所示,你应该对在真实世界的脚本中使用它们犹豫不决。它们几乎可以保证使您的代码比必要的更令人困惑。通常,您可以使用关联数组获得所需的功能。

答案 1 :(得分:13)

这是一种没有评估的方法。

请参阅此处描述的 Bash技巧#2 http://mywiki.wooledge.org/BashFAQ/006

似乎在bash 3及以上版本中工作。

#!/bin/bash

ARRAYNAME='FRUITS'
tmp=$ARRAYNAME[@]
FRUITS=( APPLE BANANA ORANGE "STAR FRUIT" )
for FRUIT in "${!tmp}"
do
    echo "${FRUIT}"
done

这是一个更现实的例子,展示了如何通过引用函数传递数组:

pretty_print_array () {
  local arrayname=$1
  local tmp=$arrayname[@]
  local array=( "${!tmp}" )
  local FS=', ' # Field seperator
  local var
  # Print each element enclosed in quotes and separated by $FS
  printf -v var "\"%s\"$FS" "${array[@]}"
  # Chop trailing $FS
  var=${var%$FS}
  echo "$arrayname=($var)"
}
FRUITS=( APPLE BANANA ORANGE "STAR FRUIT" )
pretty_print_array FRUITS
# prints FRUITS=("APPLE", "BANANA", "ORANGE", "STAR FRUIT")

答案 2 :(得分:3)

eval执行包含数组元素的代码,即使它们包含例如命令替换。它还通过解释其中的bash元字符来更改数组元素。

避免这些问题的工具是declare 参考,请参阅声明下的man bash

  

-n 为每个名称指定nameref属性,使其成为另一个变量的名称引用。另一个变量由name的值定义。除了使用或更改-n属性本身的名称外,对名称的所有引用,赋值和属性修改都是对名称值引用的变量执行的。 nameref属性不能应用于数组变量。

#!/bin/bash
declare -n ARRAYNAME='FRUITS'
FRUITS=(APPLE BANANA ORANGE "BITTER LEMON")
for FRUIT in "${ARRAYNAME[@]}"
do
    echo "${FRUIT}"
done

答案 3 :(得分:2)

这个答案来得太晚了,但是我猜想有比目前为止提出的方法更为简洁的方法(对作者的所有方面表示敬意)。

这是关于使用内置-n / declare bash的local选项的。 (有关更多信息,请在bash中输入help declare

所以我们开始:

ARRAYNAME='FRUITS';
FRUITS=(APPLE BANANA ORANGE);

# This is the critical addition. With help of option `-n` we declare
# variable `fruits` as indirect reference to another variable. Anytime
# we refer to ${fruits} we would actually refer to a variable whose
# name is stored in `fruits` variable:
declare -n fruits="${ARRAYNAME}";

# Here we use ${fruits} as ordinary variable, but in reality it refers
# to `FRUITS` variable:
for FRUIT in ${fruits[@]}; do
    echo "${FRUIT}";
done;

结果是:

APPLE
BANANA
ORANGE

答案 4 :(得分:0)

我只是想添加另一个有用的用例。我正在网上搜索一个不同但相关问题的解决方案

ARRAYNAME=( FRUITS VEG )
FRUITS=( APPLE BANANA ORANGE )
VEG=( CARROT CELERY CUCUMBER )
for I in "${ARRAYNAME[@]}"
do
    array="${I}[@]"
    for fruit in "${!array}"; do
        echo $fruit
    done
done

答案 5 :(得分:0)

尽管OP问题很简单,但这些答案不会针对最常见的实际用例进行扩展,即包含空格或通配符的数组元素,这些数据元素尚未扩展为文件名。

FRUITS=( APPLE BANANA ORANGE 'not broken' '*.h')
ARRAYNAME=FRUITS
eval ARRAY=\(\${$ARRAYNAME[@]}\)

$ echo "${ARRAY[4]}"
broken
$ echo "${ARRAY[5]}"
config.h
$

这有效:

FRUITS=( APPLE BANANA ORANGE 'not broken' '*.h')
ARRAYNAME=FRUITS
eval ARRAY="(\"\${$ARRAYNAME[@]}\")"

$ echo "${ARRAY[3]}"
not broken
$ echo "${ARRAY[4]}"
*.h
$

就像你应该习惯使用"$@"而不是$@一样,总是在( )内引用数组扩展,除非你想要文件名扩展或知道&#39 ; s数组元素不可能包含空格。

执行此操作:X=("${Y[@]}")

不是这个:X=(${Y[@]})