Nome da variável concatenada da desreferência

12

Eu posso fazer isso, mas é necessário criar uma sequência da variável e desreferencia-la. Existe alguma maneira de encurtar para uma declaração mais simples?

#!/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

Eu tentei muitas coisas como ${!"${fruit}_COLOUR"}ou \$${fruit}_COLOURe muitas outras variantes, mas a única maneira que funcionou é usando uma string.

Esteira
fonte
A !sintaxe é um sinalizador na substituição de variável que basicamente diz "substituir duas vezes". Não altera o requisito de que o conteúdo esteja dentro do ${…}nome da variável. Portanto, não, você não pode evitar o uso de uma variável que contém o nome, a menos que use um método diferente ( eval).
Gilles 'SO- stop be evil'
Você também pode usar matrizes associativas para fazer isso de maneira muito mais limpa. No entanto, não é uma resposta direta à sua pergunta, apenas adicione-a como um comentário.
217 Patrick Patrick

Respostas:

10

Primeiro, você não precisa usar $(echo $FRUITS)a fordeclaração. Usar apenas $FRUITSé suficiente. Então você pode acabar com uma das linhas dentro do loop, usando eval.

O evalsimplesmente diz ao bash para fazer uma segunda avaliação da seguinte declaração (ou seja, uma a mais que a sua avaliação normal). Ele \$sobrevive à primeira avaliação como $e a próxima avaliação trata isso $como o início de um nome de variável, que resolve como "Amarelo" etc.

Dessa forma, você não precisa ter uma etapa separada que faça uma sequência intermediária (que é o que acredito ser a principal intenção da sua pergunta).

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

Para um método alternativo, como mencionado por Patrick em um comentário (acima), você pode usar uma matriz associativa , na qual o índice de um elemento não precisa ser um número inteiro. Você pode usar uma sequência, como o nome de um tipo de fruta. Aqui está um exemplo, usando a matriz associativa do 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
fonte
6

Você pode usar o bash-builtin evalpara fazer isso:

#!/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

Observe o sinal de dólar com barra invertida. Basicamente, a linha "eval" faz bash com que substitua $fruite ${fruit_color}, em seguida, use evalpara fazer uma segunda rodada de subestação antes de chamar echo.

Bruce Ediger
fonte
1

Você não precisa de uma declaração de avaliação. evalé uma dica na maioria dos idiomas de que você está fazendo algo errado.

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}"
Joseph Lust
fonte
Eu diria que é mais seguro usar evalporque pelo menos as pessoas sabem que é perigoso, ${!var}é quase tão perigoso quanto bash(também uma vulnerabilidade de injeção de comando se $chosen_prefixestiver sob o controle de um invasor) e menos pessoas estão cientes disso. Tente var_name='a[$(uname>&2)0]' bash -c 'v=${!var_name}'. A melhor abordagem aqui é a matriz associativa.
Stéphane Chazelas