Nombre de variable concatenado de desreferencia

12

Puedo hacer esto, pero requiere hacer una cadena de la variable y luego desreferenciarla. ¿Hay alguna forma de acortarlo a una declaración más simple?

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

He intentado muchas cosas como ${!"${fruit}_COLOUR"}o \$${fruit}_COLOURy muchas otras variantes, pero la única forma en que ha funcionado es usando una cadena.

Estera
fuente
La !sintaxis es un indicador de sustitución de variables que básicamente dice "sustituir dos veces". No cambia el requisito de que lo que hay dentro ${…}debe ser un nombre de variable. Entonces, no, no puede evitar usar una variable que contenga el nombre, a menos que use un método diferente ( eval).
Gilles 'SO- deja de ser malvado'
También puede usar matrices asociativas para hacer esto de manera mucho más limpia. Sin embargo, no es una respuesta directa a tu pregunta, así que solo agrégala como comentario.
Patrick

Respuestas:

11

En primer lugar, no necesita usar $(echo $FRUITS)en la fordeclaración. Usar solo $FRUITSes suficiente. Luego, puede eliminar una de las líneas dentro del bucle utilizando eval.

El evalsimplemente le dice a bash para realizar una segunda evaluación de la siguiente declaración (es decir, uno más que su evaluación normal). La \$sobrevive a la primera evaluación, $y la próxima evaluación a continuación, trata a este $como el inicio de un nombre de variable, que se resuelve a "Amarillo", etc.

De esta manera, no necesita tener un paso separado que haga una cadena provisional (que es lo que creo que fue la intención principal de su pregunta).

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

Para un método alternativo, como mencionó Patrick en un comentario (arriba), puede usar una matriz asociativa , en la que el índice de un elemento no necesita ser un número entero. Puede usar una cadena, como el nombre de un tipo de fruta. Aquí hay un ejemplo, usando la matriz asociativa de 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
fuente
6

Puede usar bash-builtin evalpara hacer eso:

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

Tenga en cuenta el signo de dólar con barra invertida. Básicamente, la línea "eval" hace bash que sustituya $fruity ${fruit_color}, luego, use evalpara hacer una segunda ronda de sustitución antes de llamar echo.

Bruce Ediger
fuente
1

No necesita una declaración eval. evales una pista en la mayoría de los idiomas de que estás haciendo algo mal.

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
fuente
Yo diría que es más seguro de usar evalporque al menos las personas saben que es peligroso, ${!var}es casi tan peligroso bash(también es una vulnerabilidad de inyección de comando si $chosen_prefixestá bajo el control de un atacante) y menos personas lo saben. Tratar var_name='a[$(uname>&2)0]' bash -c 'v=${!var_name}'. El mejor enfoque aquí es la matriz asociativa.
Stéphane Chazelas