web-dev-qa-db-fra.com

Noms de variables dynamiques dans Bash

Je suis confus à propos d'un script bash.

J'ai le code suivant:

function grep_search() {
    magic_way_to_define_magic_variable_$1=`ls | tail -1`
    echo $magic_variable_$1
}

Je veux pouvoir créer un nom de variable contenant le premier argument de la commande et portant la valeur de p. Ex. la dernière ligne de ls.

Alors pour illustrer ce que je veux:

$ ls | tail -1
stack-overflow.txt

$ grep_search() open_box
stack-overflow.txt

Alors, comment dois-je définir/déclarer $magic_way_to_define_magic_variable_$1 et comment dois-je l'appeler dans le script?

J'ai essayé eval, ${...}, \$${...}, mais je suis toujours confus.

104
Konstantinos

Utilisez un tableau associatif, avec les noms de commande comme clés.

# Requires bash 4, though
declare -A magic_variable=()

function grep_search() {
    magic_variable[$1]=$( ls | tail -1 )
    echo ${magic_variable[$1]}
}

Si vous ne pouvez pas utiliser de tableaux associatifs (par exemple, vous devez prendre en charge bash 3), vous pouvez utiliser declare pour créer des noms de variables dynamiques:

declare "magic_variable_$1=$(ls | tail -1)"

et utilisez l’extension de paramètre indirect pour accéder à la valeur.

var="magic_variable_$1"
echo "${!var}"

Voir BashFAQ: Indirection - Evaluation des variables indirectes/de référence .

109
chepner

Je cherchais un meilleur moyen de le faire récemment. Le tableau associatif me paraissait excessif. Regarde ce que j'ai trouvé:

suffix=bzz
declare prefix_$suffix=mystr

...et alors...

varname=prefix_$suffix
echo ${!varname}
179
Yorik.sar

L'exemple ci-dessous renvoie la valeur de $ name_of_var

var=name_of_var
echo $(eval echo "\$$var")
14
meow meow

Cela devrait fonctionner:

function grep_search() {
    declare magic_variable_$1="$(ls | tail -1)"
    echo "$(tmpvar=magic_variable_$1 && echo ${!tmpvar})"
}
grep_search var  # calling grep_search with argument "var"
4
Jahid

Cela fonctionnera aussi

my_country_code="green"
x="country"

eval z='$'my_"$x"_code
echo $z                 ## o/p: green

Dans ton cas

eval final_val='$'magic_way_to_define_magic_variable_"$1"
echo $final_val
3
k_vishwanath

Wow, la syntaxe est en grande partie horrible! Voici une solution avec une syntaxe plus simple si vous avez besoin de référencer indirectement des tableaux: 

#!/bin/bash

foo_1=("fff" "ddd") ;
foo_2=("ggg" "ccc") ;

for i in 1 2 ;
do
    eval mine=( \${foo_$i[@]} ) ;
    echo ${mine[@]} ;
done ;

Pour des cas d'utilisation plus simples, je recommande la syntaxe décrite dans le Advanced Bash-Scripting Guide .

2
ingyhere

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 supposerai que i=37 et que 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 indirectionnelle, un peu comme un pointeur C. Bash a alors une syntaxe pour lecture la variable avec alias: ${!name} se développe en valeur de la variable dont le nom est la valeur de la variable name. Vous pouvez le considérer comme une extension en deux étapes: ${!name} se 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 de contrepartie pour modification la variable avec alias. Au lieu de cela, vous pouvez réaliser l’affectation avec l’une des astuces suivantes.

1a. Assigner avec eval

eval est mauvais, mais c'est aussi le moyen le plus simple et le plus portable d'atteindre notre objectif. Vous devez soigneusement échapper le côté droit de la tâche, car elle sera évaluée deux fois. Une façon simple et systématique de procéder consiste à évaluer préalablement le membre de droite (ou à 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 (que se passe-t-il s'il s'agit de 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 diabolique.
  • _ {eval is evil.
  • eval est le mal.

1b. Assigner avec read

La variable intégrée read vous permet d'affecter des valeurs à une variable dont vous donnez le nom, ce qui peut être exploité en conjonction avec here-strings:

IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37"  # outputs “babibab\n”

La partie IFS et l'option -r s'assurent que la valeur est affectée telle quelle, tandis que l'option -d '' permet d'affecter des valeurs multilignes. En raison de cette dernière option, la commande retourne avec un code de sortie non nul.

Notez que, puisque nous utilisons une chaîne here, un caractère de nouvelle ligne est ajouté à la valeur.

Inconvénients:

  • un peu obscure;
  • retourne avec un code de sortie non nul;
  • ajoute une nouvelle ligne à la valeur.

1c Assigner avec printf

Depuis Bash 3.1 (publié en 2005), la variable intégrée printf 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 la scission, 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 (publié en 2014), la variable intégrée declare a une option -n permettant de 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 avec alias, mais chaque fois que l'on accède à la référence (pour la lecture ou l'affectation), Bash résout automatiquement l'indirection.

De plus, Bash a une syntaxe spéciale et très déroutante pour obtenir la valeur de la référence elle-même, jugez 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 simplifie la syntaxe.

Inconvénients:

  • Pas portable.

Des risques

Toutes ces techniques de repliement présentent plusieurs risques. Le premier est exécutant du code arbitraire chaque fois que vous résolvez l'indirection (soit en lecture, soit en affectation)}. En effet, au lieu d’un nom de variable scalaire, tel que var_37, vous pouvez également aliaser un indice de tableau, tel que arr[42]. Mais Bash évalue le contenu des crochets à chaque fois que cela est nécessaire, de sorte que l'aliasing arr[$(do_evil)] aura des effets inattendus… En conséquence, n'utilise 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 de créer un alias cyclique. Comme les variables Bash sont identifiées par leur nom et non par leur portée, vous pouvez créer par inadvertance un alias pour lui-même (tout en pensant qu'il alias une variable d'une portée englobante). Cela peut se produire en particulier lorsque vous utilisez des noms de variables communs (tels que var). Par conséquent, n’utilisez ces techniques que lorsque vous contrôlez le nom de la variable avec alias .

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!

Source:

2
Maëlan

Pour les tableaux indexés, vous pouvez les référencer de la manière suivante:

foo=(a b c)
bar=(d e f)

for arr_var in 'foo' 'bar'; do
    declare -a 'arr=("${'"$arr_var"'[@]}")'
    # do something with $arr
    echo "\$$arr_var contains:"
    for char in "${arr[@]}"; do
        echo "$char"
    done
done

Les tableaux associatifs peuvent être référencés de la même manière mais nécessitent le commutateur -A sur declare au lieu de -a.

1
Walf

Je veux pouvoir créer un nom de variable contenant le premier argument de la commande

script.sh fichier:

#!/usr/bin/env bash
function grep_search() {
  eval $1=$(ls | tail -1)
}

Tester:

$ source script.sh
$ grep_search open_box
$ echo $open_box
script.sh

Selon help eval:

Exécuter des arguments en tant que commande Shell.


Vous pouvez également utiliser l'expansion indirecte Bash ${!var}, comme déjà mentionné, mais cela ne prend pas en charge la récupération des index de tableaux.


Pour une lecture plus poussée ou des exemples, consultez BashFAQ/006 à propos de Indirection .

Nous ne sommes au courant d'aucune astuce susceptible de dupliquer cette fonctionnalité dans des shells POSIX ou Bourne sans eval, ce qui peut être difficile à exécuter en toute sécurité. Donc, considérez ceci comme une utilisation à vos risques et périls.

Cependant, vous devriez reconsidérer l'utilisation de l'indirection selon les remarques suivantes.

Normalement, dans les scripts bash, vous n’avez pas du tout besoin de références indirectes. Généralement, les gens recherchent une solution lorsqu'ils ne comprennent pas ou ne connaissent pas Bash Arrays ou ne considèrent pas pleinement les autres fonctionnalités de Bash, telles que les fonctions.

Mettre des noms de variables ou toute autre syntaxe bash dans les paramètres est souvent mal fait et dans des situations inappropriées pour résoudre des problèmes offrant de meilleures solutions. Cela viole la séparation entre code et données et, en tant que tel, vous place sur une pente glissante vers les bogues et les problèmes de sécurité. L'indirection peut rendre votre code moins transparent et plus difficile à suivre.

0
kenorb

Selon BashFAQ/006 , vous pouvez utiliser read avec ici la syntaxe de chaîne pour affecter des variables indirectes:

function grep_search() {
  read "$1" <<<$(ls | tail -1);
}

Usage:

$ grep_search open_box
$ echo $open_box
stack-overflow.txt
0
kenorb