Ім'я змінної зв'язаної дереференції


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

6

Для цього можна використовувати bash-вбудований 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}'. Найкращим підходом тут є асоціативний масив.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.