如何使用间接引用遍历数组?

Posted

技术标签:

【中文标题】如何使用间接引用遍历数组?【英文标题】:How to iterate over an array using indirect reference? 【发布时间】:2012-06-26 04:47:29 【问题描述】:

我怎样才能使这段代码工作?

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

这段代码:

echo $!ARRAYNAME[0]

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

提前致谢,

【问题讨论】:

请看BashFAQ/006。 【参考方案1】:

$!ARRAYNAME[@] 表示“ARRAYNAME 的索引”。如bash man page 中所述,因为设置了ARRAYNAME,但作为字符串而不是数组,它返回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 中引入的,旨在在很大程度上取代 eval 在尝试在 shell 中实现类似反射的行为时的需求。

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

#!/usr/bin/env bash

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

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

总而言之,在这个简单的示例中使用间接引用是一回事,但是,正如 Dennis Williamson 提供的链接所示,您应该对在实际脚本中使用它们犹豫不决。它们几乎可以保证使您的代码比必要的更加混乱。通常,您可以使用关联数组获得所需的功能。

【讨论】:

蒂姆:你的第二部分答案没有用,因为 ARRAYNAME 是我脚本的输入参数。但是您的第一个解决方案让我找到了一个更优雅的解决方案: ARRAY=$!ARRAYNAME; $ARRAY[@] 中的 FRUIT...谢谢 @Neuquino 查看我编辑的答案。您需要做的就是在输入变量的末尾连接[@] @Neuquino 如果 ARRAYNAME 是您脚本的输入参数,那么您做错了什么。将用户输入与变量名混合使用的理由为零。在这种情况下(结合间接扩展),它允许任意代码注入。这些技术的唯一正当理由是在函数中使用,从不在全局范围内,并且从不与用户输入结合。 @ormaaj ARRAYNAME 不是输入参数。我有一个 KEYS=( FRUITS VEGETABLES MEAT ) 的配置文件 (config.conf),然后是 FRUITS=( APPLE BANANA ORANGE )\n VEGETABLES=( BEANS LETTUCE )\n MEAT=( CHICKEN ROAST_BEAF ) 的多行。然后在我的脚本中执行 source config.conf。 @Neuquino 如果您可以信任输入应该没问题(在这种情况下是文件)。【参考方案2】:

这是一种无需 eval 的方法。

请参阅此处描述的 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")

【讨论】:

有什么方法可以在没有 eval 的情况下获得这种间接数组的大小?【参考方案3】:

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

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

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

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

【讨论】:

谢谢。目前,使用 Bash 4.3+,这似乎是最好和最简单的答案。它应该被投票给顶部。请注意,文档中的句子“nameref 属性不能应用于数组变量”是令人困惑的并且可能具有误导性,因为它似乎暗示它不能用于访问数组变量。但是,它确实有效。【参考方案4】:

这个答案来得太晚了,但我想有比目前提出的更简洁的方法(尊重他们的作者)。

这是关于使用declare/local bash 内置的-n 选项。 (有关更多信息,请在 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

【讨论】:

【参考方案5】:

尽管是简单的 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
$

正如您应该养成使用"$@" 而不是$@ 的习惯一样,始终在( ) 内引用以进行数组扩展,除非您想要扩展文件名或知道数组元素不可能包含空格。

这样做:X=("$Y[@]")

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

【讨论】:

【参考方案6】:

我认为他的问题的正确方法和最佳答案与实际的间接引用有关,对提问者的原始代码进行最少的修改,您甚至可以使用关联数组来做到这一点。

OP 的最小修改代码

declare -n ARRAYNAME='FRUITS'
declare -a FRUITS=( APPLE BANANA ORANGE )
for FRUIT in $!ARRAYNAME[@]
do
    echo "$ARRAYNAME[$FRUIT]"
done

输出

APPLE
BANANA
ORANGE

在关联数组中的使用

declare -A associative_array
declare -n array_name=associative_array
associative_array[kittens]='cat'
associative_array[puppies]='dog'
associative_array[kitties]='cat'
associative_array[doggies]='dog'
for name in $!array_name[@] ; do 
echo $name has the value of "$associative_array[$name]"
done

输出:

puppies has the value of dog
kittens has the value of cat
kitties has the value of cat
doggies has the value of dog

不必阅读整个 bash 手册页,只需使用内置的 help (

$ help help
help: help [-dms] [pattern ...]
    Display information about builtin commands.
    
    Displays brief summaries of builtin commands.  If PATTERN is
    specified, gives detailed help on all commands matching PATTERN,
    otherwise the list of help topics is printed.
    
    Options:
      -d    output short description for each topic
      -m    display usage in pseudo-manpage format
      -s    output only a short usage synopsis for each topic matching
            PATTERN
    
    Arguments:
      PATTERN   Pattern specifying a help topic
    
    Exit Status:
    Returns success unless PATTERN is not found or an invalid option is given.

)

声明的用法:

declare: declare [-aAfFgilnrtux] [-p] [name[=value] ...]
    Set variable values and attributes.
    
    Declare variables and give them attributes.  If no NAMEs are given,
    display the attributes and values of all variables.
    
    Options:
      -f    restrict action or display to function names and definitions
      -F    restrict display to function names only (plus line number and
            source file when debugging)
      -g    create global variables when used in a shell function; otherwise
            ignored
      -p    display the attributes and value of each NAME
    
    Options which set attributes:
      -a    to make NAMEs indexed arrays (if supported)
      -A    to make NAMEs associative arrays (if supported)
      -i    to make NAMEs have the `integer' attribute
      -l    to convert the value of each NAME to lower case on assignment
      -n    make NAME a reference to the variable named by its value
      -r    to make NAMEs readonly
      -t    to make NAMEs have the `trace' attribute
      -u    to convert the value of each NAME to upper case on assignment
      -x    to make NAMEs export
    
    Using `+' instead of `-' turns off the given attribute.
    
    Variables with the integer attribute have arithmetic evaluation (see
    the `let' command) performed when the variable is assigned a value.
    
    When used in a function, `declare' makes NAMEs local, as with the `local'
    command.  The `-g' option suppresses this behavior.
    
    Exit Status:
    Returns success unless an invalid option is supplied or a variable
    assignment error occurs.

【讨论】:

【参考方案7】:

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

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

【讨论】:

array="$I[@]"tmp=$arrayname[@] 完全相同!没有任何改善!

以上是关于如何使用间接引用遍历数组?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 数组(及二维数组)与指针(互转遍历),数组与引用

怎么遍历只有id和pid 的数组排序

最大公约数遍历

Java 如何遍历数组里面的数据?

list遍历陷阱分析原理

PHP中如何使用foreach结构遍历数组?