Как перебирать массив с помощью косвенной ссылки?

Как я могу заставить этот код работать?

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

Этот код:

echo ${!ARRAYNAME[0]}

Печать APPLE. Я пытаюсь сделать что-то подобное, но с "[@]" для итерации по массиву.

Спасибо заранее,

Ответы

Ответ 1

${!ARRAYNAME[@]} означает "индексы ARRAYNAME". Как указано в bash странице man, поскольку ARRAYNAME установлен, но как строка, а не массив, он возвращает 0.

Здесь решение с использованием eval.

#!/usr/bin/env bash

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

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

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

То, что вы изначально пытались сделать, это создать косвенную ссылку. Они были представлены в версии bash версии 2 и были предназначены для значительной замены необходимости eval при попытке добиться отражения в оболочке.

Что вы должны делать при использовании косвенных ссылок с массивами, это включить [@] в ваше предположение при имени переменной:

#!/usr/bin/env bash

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

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

Все, что сказало, одно дело использовать косвенные ссылки в этом тривиальном примере, но, как указано в ссылке, предоставленной Деннисом Уильямсоном, вы должны не решаться использовать их в реальных сценариях. Они почти гарантированно делают ваш код более запутанным, чем необходимо. Обычно вы можете получить необходимую функциональность с помощью ассоциативного массива.

Ответ 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")

Ответ 3

Я просто хотел добавить еще один полезный случай использования. Я искал в Интернете решение для другой, но связанной проблемы

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

Несмотря на простой вопрос с ОП, эти ответы не будут масштабироваться для наиболее распространенных реальных случаев использования, т.е. элементов массива, содержащих пробельные символы или подстановочные знаки, которые еще не должны быть расширены до имен файлов.

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
$

Так же, как вы привыкли использовать "[email protected]" not [email protected], всегда указывайте внутри ( ) для расширений массива, если вы не хотите расширения имени файла или не знаете возможности элементов массива, содержащих пробелы.

Сделайте это: X=("${Y[@]}")

Не это: X=(${Y[@]})

Ответ 5

eval выполняет код, содержащий элементы массива, даже если они содержат, например, подстановки команд. Он также изменяет элементы массива, интерпретируя в них bash метасимволы.

Единственный инструмент, который позволяет избежать этих проблем, - это ссылка:

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