Как перебрать массив, используя косвенную ссылку?
Как я могу заставить этот код работать?
#!/bin/bash
ARRAYNAME='FRUITS'
FRUITS=( APPLE BANANA ORANGE )
for FRUIT in ${!ARRAYNAME[@]}
do
echo ${FRUIT}
done
Этот код:
echo ${!ARRAYNAME[0]}
Печатает ЯБЛОКО. Я пытаюсь сделать что-то подобное, но с "[@]", чтобы перебрать массив.
Заранее спасибо,
7 ответов
${!ARRAYNAME[@]}
означает "индексы ARRAYNAME
". Как сказано в справочной странице bash, так как 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
Все это говорит об одном: использовать косвенные ссылки в этом тривиальном примере однозначно, но, как указано в ссылке, предоставленной Деннисом Уильямсоном, вам не следует использовать их в реальных сценариях. Они гарантированно сделают ваш код более запутанным, чем необходимо. Обычно вы можете получить необходимую вам функциональность с помощью ассоциативного массива.
Вот способ сделать это без 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
выполняет код, содержащий элементы массива, даже если они содержат, например, подстановки команд. Он также изменяет элементы массива, интерпретируя в них метасимволы 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
Этот ответ приходит очень поздно, но я полагаю, что существует гораздо более чистый подход, чем те, которые были представлены до этого момента (со всем уважением к их авторам).
Речь идет об использовании -n
вариант declare
/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
Я думаю, что правильный способ и лучший ответ на его вопрос связаны с фактической косвенной ссылкой, вносят наименьшие изменения в исходный код спрашивающего, и вы даже можете сделать это с помощью ассоциативного массива.
Минимально модифицированный код для 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.
Несмотря на простой вопрос 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[@]})
Я просто хотел добавить еще один полезный вариант использования. Я искал в Интернете решение другой, но связанной проблемы
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