How to iterate over an array using indirect reference?

匿名 (未验证) 提交于 2019-12-03 02:16:02

问题:

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.

Thanks in advance,

回答1:

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

Here's a solution using 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 eval when trying to achieve reflection-like behavior in the 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.



回答2:

Here's a way to do it without eval.

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

Seems to work in bash 3 and up.

#!/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=$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") 


回答3:

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 


回答4:

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.

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.

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

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



回答5:

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

The single tool that avoids these problems is a reference:

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


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!