Au-delà des tableaux associatifs, il existe plusieurs façons d'obtenir des variables dynamiques dans Bash. Notez que toutes ces techniques présentent des risques, qui sont discutés à la fin de cette réponse.
Dans les exemples suivants, je suppose que i=37
vous souhaitez aliaser la variable nommée var_37
dont la valeur initiale est lolilol
.
Méthode 1. Utilisation d'une variable «pointeur»
Vous pouvez simplement stocker le nom de la variable dans une variable d'indirection, un peu comme un pointeur C. Bash a alors une syntaxe pour lire la variable aliasée: se ${!name}
développe à la valeur de la variable dont le nom est la valeur de la variable name
. Vous pouvez le considérer comme une expansion en deux étapes: se ${!name}
développe en $var_37
, qui se développe en lolilol
.
name="var_$i"
echo "$name" # outputs “var_37”
echo "${!name}" # outputs “lolilol”
echo "${!name%lol}" # outputs “loli”
# etc.
Malheureusement, il n'y a pas de syntaxe homologue pour modifier la variable aliasée. Au lieu de cela, vous pouvez réaliser une affectation avec l'une des astuces suivantes.
1a. Assigner aveceval
eval
est le mal, mais c'est aussi le moyen le plus simple et le plus portable d'atteindre notre objectif. Vous devez soigneusement échapper au côté droit de la tâche, car elle sera évaluée deux fois . Une manière simple et systématique de le faire est d'évaluer le côté droit au préalable (ou de l'utiliser printf %q
).
Et vous devriez vérifier manuellement que le côté gauche est un nom de variable valide, ou un nom avec index (et si c'était le cas evil_code #
?). En revanche, toutes les autres méthodes ci-dessous l'appliquent automatiquement.
# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit
value='babibab'
eval "$name"='$value' # carefully escape the right-hand side!
echo "$var_37" # outputs “babibab”
Inconvénients:
- ne vérifie pas la validité du nom de la variable.
eval
est le mal.
eval
est le mal.
eval
est le mal.
1b. Assigner avecread
Le read
builtin vous permet d'assigner des valeurs à une variable dont vous donnez le nom, un fait qui peut être exploité en conjonction avec des chaînes ici:
IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37" # outputs “babibab\n”
La IFS
pièce et l'option -r
s'assurent que la valeur est attribuée telle quelle, tandis que l'option -d ''
permet d'attribuer des valeurs multilignes. En raison de cette dernière option, la commande retourne avec un code de sortie différent de zéro.
Notez que, puisque nous utilisons une chaîne ici, un caractère de nouvelle ligne est ajouté à la valeur.
Inconvénients:
- quelque peu obscur;
- retourne avec un code de sortie différent de zéro;
- ajoute une nouvelle ligne à la valeur.
1c. Assigner avecprintf
Depuis Bash 3.1 (sorti en 2005), le printf
builtin peut également affecter son résultat à une variable dont le nom est donné. Contrairement aux solutions précédentes, cela fonctionne, aucun effort supplémentaire n'est nécessaire pour échapper aux choses, pour éviter le fractionnement, etc.
printf -v "$name" '%s' 'babibab'
echo "$var_37" # outputs “babibab”
Inconvénients:
- Moins portable (mais bon).
Méthode 2. Utilisation d'une variable «référence»
Depuis Bash 4.3 (sorti en 2014), le declare
builtin a une option -n
pour créer une variable qui est une «référence de nom» à une autre variable, un peu comme les références C ++. Tout comme dans la méthode 1, la référence stocke le nom de la variable aliasée, mais chaque fois que la référence est accédée (que ce soit pour la lecture ou l'affectation), Bash résout automatiquement l'indirection.
En outre, Bash a une syntaxe spéciale et très déroutant pour obtenir la valeur de la référence elle - même, juge par vous - même: ${!ref}
.
declare -n ref="var_$i"
echo "${!ref}" # outputs “var_37”
echo "$ref" # outputs “lolilol”
ref='babibab'
echo "$var_37" # outputs “babibab”
Cela n'évite pas les pièges expliqués ci-dessous, mais au moins cela rend la syntaxe simple.
Inconvénients:
Des risques
Toutes ces techniques d'aliasing présentent plusieurs risques. Le premier exécute du code arbitraire chaque fois que vous résolvez l'indirection (soit pour la lecture, soit pour l'affectation) . En effet, au lieu d'un nom de variable scalaire, comme var_37
, vous pouvez aussi aliaser un indice de tableau, comme arr[42]
. Mais Bash évalue le contenu des crochets à chaque fois que cela est nécessaire, donc l'alias arr[$(do_evil)]
aura des effets inattendus ... Par conséquent, n'utilisez ces techniques que lorsque vous contrôlez la provenance de l'alias .
function guillemots() {
declare -n var="$1"
var="«${var}»"
}
arr=( aaa bbb ccc )
guillemots 'arr[1]' # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]' # writes twice into date.out
# (once when expanding var, once when assigning to it)
Le deuxième risque est la création d'un alias cyclique. Comme les variables Bash sont identifiées par leur nom et non par leur portée, vous pouvez par inadvertance créer un alias pour elle-même (tout en pensant que cela alias une variable à partir d'une portée englobante). Cela peut se produire en particulier lors de l'utilisation de noms de variables communs (comme var
). Par conséquent, n'utilisez ces techniques que lorsque vous contrôlez le nom de la variable aliasée .
function guillemots() {
# var is intended to be local to the function,
# aliasing a variable which comes from outside
declare -n var="$1"
var="«${var}»"
}
var='lolilol'
guillemots var # Bash warnings: “var: circular name reference”
echo "$var" # outputs anything!
La source: