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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11180714/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 02:34:53  来源:igfitidea点击:

How to iterate over an array using indirect reference?

bashscriptingindirection

提问by Neuquino

How can I make this code work?

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

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

This code:

这段代码:

echo ${!ARRAYNAME[0]}

Prints APPLE. I'm tryng to do something similar but with "[@]" to iterate over the array.

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

Thanks in advance,

提前致谢,

回答by Tim Pote

${!ARRAYNAME[@]}means "the indices of ARRAYNAME". As stated in the bash man pagesince ARRAYNAMEis set, but as a string, not an array, it returns 0.

${!ARRAYNAME[@]}表示“的索引ARRAYNAME”。正如bash 手册页中所述,因为ARRAYNAME已设置,但作为字符串而不是数组,它返回0.

Here's a solution using eval.

这是使用eval.

#!/usr/bin/env bash

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

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

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


What you were originally trying to do was create an Indirect Reference. These were introduced in bash version 2 and were meant to largely replace the need for evalwhen trying to achieve reflection-like behavior in the shell.

您最初尝试做的是创建一个Indirect Reference。这些是在 bash 版本 2 中引入的,旨在在很大程度上取代eval在 shell 中尝试实现类似反射的行为时的需要。

What you have to do when using indirect references with arrays is include the [@]in your guess at the variable name:

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

#!/usr/bin/env bash

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

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


All that said, it's one thing to use Indirect References in this trivial example, but, as indicated in the link provided by Dennis Williamson, you should be hesitant to use them in real-world scripts. They are all but guaranteed to make your code more confusing than necessary. Usually you can get the functionality you need with an Associative Array.

尽管如此,在这个简单的例子中使用间接引用是一回事,但是,正如 Dennis Williamson 提供的链接中所指出的,您应该犹豫在现实世界的脚本中使用它们。它们几乎肯定会使您的代码比必要的更加混乱。通常,您可以使用关联数组获得所需的功能。

回答by Robin A. Meade

Here's a way to do it without eval.

这是一种无需 eval 即可完成的方法。

See Bash trick #2described here: http://mywiki.wooledge.org/BashFAQ/006

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

Seems to work in bash 3 and up.

似乎适用于 bash 3 及更高版本。

#!/bin/bash

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

Here's a more realistic example showing how to pass an array by reference to a function:

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

pretty_print_array () {
  local arrayname=
  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")

回答by Hans Klünder

evalexecutes code containing array elements, even if they contain, for example, command substitutions. It also changes the array elements by interpreting bash metacharacters in them.

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

A tool that avoids these problems is the declarereference, see man bashunder declare:

避免这些问题的工具是declarereference,请参阅man bash下面的声明:

-nGive each name the nameref attribute, making it a name reference to another variable. That other variable is defined by the value of name. All references, assignments, and attribute modifications to name, except those using or changing the -n attribute itself, are performed on the variable referenced by name's value. The nameref attribute cannot be applied to array variables.

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

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

回答by Cromax

This answer comes very late, but I guess there is much cleaner approach than those presented up to this moment (with all the respects to their authors).

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

It is about using -noption of declare/localbash built-in. (For more info type help declarein your bash).

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

So here we go:

所以我们开始:

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;

And the result is:

结果是:

APPLE
BANANA
ORANGE

回答by Felipe Alvarez

I just wanted to add another useful use-case. I was searching the web for a solution to a different, but related problem

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

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

回答by Felipe Alvarez

Despite the simple OP question, these answers won't scale for the most common, real use-cases, i.e., array elements containing whitespace or wildcards that should not yet be expanded to filenames.

尽管有一个简单的 OP 问题,但这些答案不适用于最常见的实际用例,即包含不应扩展为文件名的空格或通配符的数组元素。

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

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

This works:

这有效:

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

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

Just as you should get in the habit of using "$@"not $@, always quote inside ( )for array expansions, unless you want filename expansion or know there's no possibility of array elements containing whitespace.

正如您应该养成使用"$@"not的习惯$@,总是在( )数组扩展时引用 inside ,除非您想要文件名扩展或知道数组元素不可能包含空格。

Do this: X=("${Y[@]}")

做这个: X=("${Y[@]}")

Not this: X=(${Y[@]})

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