Nom de variable concaténée de déréférence

12

Je peux le faire, mais cela nécessite de faire une chaîne de la variable puis de la déréférencer. Existe-t-il un moyen de le raccourcir en une déclaration plus 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

J'ai essayé beaucoup de choses comme ${!"${fruit}_COLOUR"}ou \$${fruit}_COLOURet de nombreuses autres variantes, mais la seule façon qui a fonctionné est d'utiliser une chaîne.

Tapis
la source
La !syntaxe est un indicateur sur la substitution de variable qui dit essentiellement «remplacer deux fois». Cela ne change pas l'exigence que ce qui est à l'intérieur du ${…}doit être un nom de variable. Donc non, vous ne pouvez pas éviter d'utiliser une variable qui contient le nom, sauf si vous utilisez une méthode différente ( eval).
Gilles 'SO- arrête d'être méchant'
Vous pouvez également utiliser des tableaux associatifs pour le faire beaucoup plus proprement. Cependant, ce n'est pas une réponse directe à votre question, alors ajoutez-la simplement en tant que commentaire.
Patrick

Réponses:

11

Tout d'abord, vous n'avez pas besoin d'utiliser $(echo $FRUITS)dans la fordéclaration. Utiliser juste $FRUITSsuffit. Ensuite, vous pouvez supprimer l'une des lignes à l'intérieur de la boucle, en utilisant eval.

Le evaldit simplement à bash de faire une deuxième évaluation de la déclaration suivante (c'est-à-dire une de plus que son évaluation normale). Le \$survit à la première évaluation en tant que $, et la prochaine évaluation traite ensuite cela $comme le début d'un nom de variable, qui se résout en "Jaune", etc.

De cette façon, vous n'avez pas besoin d'avoir une étape distincte qui crée une chaîne intermédiaire (ce qui est, je crois, l'objectif principal de votre question).

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

Pour une méthode alternative, comme mentionné par Patrick dans un commentaire (ci-dessus), vous pouvez utiliser à la place un tableau associatif , dans lequel l'index d'un élément n'a pas besoin d'être un entier. Vous pouvez utiliser une chaîne, comme le nom d'un type de fruit. Voici un exemple, en utilisant le tableau associatif 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
la source
6

Vous pouvez utiliser le bash-builtin evalpour ce faire:

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

Notez le signe dollar contre-oblique. Fondamentalement, la ligne «eval» fait bash remplacer $fruitet ${fruit_color}, puis utilise evalpour effectuer un deuxième tour de sous-station avant d'appeler echo.

Bruce Ediger
la source
1

Vous n'avez pas besoin d'une instruction eval. evalest un indice dans la plupart des langues que vous faites quelque chose de 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
la source
Je dirais qu'il est plus sûr à utiliser evalcar au moins les gens savent qu'il est dangereux, ${!var}est à peu près aussi dangereux bash(également une vulnérabilité d'injection de commande si elle $chosen_prefixest sous le contrôle d'un attaquant) et moins de gens en sont conscients. Essayez var_name='a[$(uname>&2)0]' bash -c 'v=${!var_name}'. La meilleure approche ici est le tableau associatif.
Stéphane Chazelas