Разыменование составной переменной name

12

Я могу сделать это, но для этого нужно создать строку переменной, а затем разыменовать ее. Есть ли способ сократить это до более простого утверждения?

#!/bin/bash

FRUITS="BANANA APPLE ORANGE"

BANANA_COLOUR="Yellow"
APPLE_COLOUR="Green or Red"
ORANGE_COLOUR="Blue"

for fruit in $( echo $FRUITS ); do
    fruit_colour="${fruit}_COLOUR"
    echo $fruit is ${!fruit_colour}
done

Я перепробовал множество вещей, таких как ${!"${fruit}_COLOUR"}или \$${fruit}_COLOURи многие другие варианты, но единственный способ, который сработал, - это использование строки.

Мат
источник
!Синтаксис флаг на переменную подстановки, в основном говорит «заменить дважды». Это не меняет требования, что внутри ${…}должно быть имя переменной. Поэтому нет, вы не можете избежать использования переменной, содержащей имя, если вы не используете другой метод ( eval).
Жиль "ТАК - перестань быть злым"
Вы также можете использовать ассоциативные массивы, чтобы сделать это намного более аккуратно. Однако это не прямой ответ на ваш вопрос, поэтому просто добавьте его в качестве комментария.
Патрик

Ответы:

11

Во-первых, вам не нужно использовать $(echo $FRUITS)в forзаявлении. Использование только $FRUITSдостаточно. Затем вы можете покончить с одной из строк внутри цикла, используя eval.

evalПросто говорит Баш , чтобы сделать вторую оценку следующего утверждения (то есть более чем одна его нормальная оценка). При \$первой оценке сохраняется как $, а при следующей оценке это рассматривается $как начало имени переменной, которая преобразуется в «Желтый» и т. Д.

Таким образом, вам не нужно иметь отдельный шаг, который составляет промежуточную строку (что, как я считаю, было основной целью вашего вопроса).

for fruit in $FRUITS ;do
    eval echo $fruit is \$${fruit}_COLOUR
done

Для альтернативного метода, как упомянуто Патриком в комментарии (выше), вместо этого вы можете использовать ассоциативный массив , в котором индекс элемента не обязательно должен быть целым числом. Вы можете использовать строку, например, название типа фруктов. Вот пример использования ассоциативного массива bash:

# This declares an associative array (It unsets it if it already exists)
declare -A colour
colour['BANANA']="Yellow"
colour["APPLE"]="Green or Red"
colour[MARTIAN ORANGE]="Blue"

for fruit in BANANA APPLE "MARTIAN ORANGE" ;do 
    echo "$fruit" is "${colour[$fruit]}"
done
Peter.O
источник
6

Вы можете использовать bash-buildin evalдля этого:

#!/bin/bash
FRUITS="BANANA APPLE ORANGE"
BANANA_COLOUR="Yellow"
APPLE_COLOUR="Green or Red"
ORANGE_COLOUR="Blue"

for fruit in $( echo $FRUITS );
do
    fruit_colour=${fruit}_COLOUR
    eval echo $fruit is \$${fruit_colour}
done

Обратите внимание на знак доллара с обратной косой чертой. По сути, строка «eval» приводит bash к замене $fruitи ${fruit_color}затем использованию evalвторого раунда подстановки перед вызовом echo.

Брюс Эдигер
источник
1

Вам не нужно утверждение Eval. evalПодсказка в большинстве языков, что вы делаете что-то не так.

foo_var=10
bar_var=12

# Build a string with your var name of choice
chosen_prefix='foo'
var_name="${chosen_prefix}_var"

# Use bang to deference it
chossen_value=${!var_name}
echo "Chossen value: ${chossen_value}"
Джозеф Ласт
источник
Я бы сказал, что безопаснее использовать, evalпотому что, по крайней мере, люди знают, что это опасно, ${!var}примерно так же опасно bash(также уязвимость внедрения команд, если $chosen_prefixнаходится под контролем злоумышленника), и меньше людей знают об этом. Попробуй var_name='a[$(uname>&2)0]' bash -c 'v=${!var_name}'. Лучший подход здесь - ассоциативный массив.
Стефан Шазелас