web-dev-qa-db-fra.com

Comment puis-je générer de nouveaux noms de variables à la volée dans un script Shell?

J'essaie de générer des noms var dynamiques dans un script Shell pour traiter un ensemble de fichiers avec des noms distincts dans une boucle comme suit:

#!/bin/bash

SAMPLE1='1-first.with.custom.name'
SAMPLE2='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ ))
do
  echo SAMPLE{$i}
done

Je m'attendrais à la sortie:

1-first.with.custom.name
2-second.with.custom.name

mais j'ai:

SAMPLE{1}
SAMPLE{2}

Est-il possible de générer des noms var à la volée?

36
pQB

Vous devez utiliser l'indirection variable:

SAMPLE1='1-first.with.custom.name'
SAMPLE2='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ ))
do
   var="SAMPLE$i"
   echo ${!var}
done

Depuis la page de manuel Bash , sous 'Expansion des paramètres':

"Si le premier caractère du paramètre est un point d'exclamation (!), Un niveau d'indirection de variable est introduit. Bash utilise la valeur de la variable formée à partir du reste du paramètre comme nom de la variable; cette variable est ensuite développée et la valeur est utilisée dans le reste de la substitution, plutôt que la valeur du paramètre lui-même. C'est ce qu'on appelle l'expansion indirecte. "

68
johnshen64

Le problème

Vous utilisez la valeur de i comme s'il s'agissait d'un index de tableau. Ce n'est pas le cas, car SAMPLE1 et SAMPLE2 sont des variables distinctes, pas un tableau.

De plus, lorsque vous appelez echo SAMPLE{$i} vous ajoutez uniquement la valeur i au mot "SAMPLE". La seule variable que vous déréférencer dans cette instruction est $ i , c'est pourquoi vous avez obtenu les résultats que vous avez obtenus.

Façons de résoudre le problème

Il existe deux façons principales de résoudre ce problème:

  1. Déréférencement en plusieurs étapes d'une variable interpolée, via le eval intégré ou expansion variable indirecte .
  2. Itération sur un tableau ou utilisation de i comme index dans un tableau.

Déréférencement avec eval

La chose la plus simple à faire dans cette situation est d'utiliser eval:

SAMPLE1='1-first.with.custom.name'
SAMPLE2='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ )); do
    eval echo \$SAMPLE${i}
done

Cela ajoutera la valeur de i à la fin de la variable, puis retraitera la ligne résultante, développant le nom de la variable interpolée (par exemple SAMPLE1 ou SAMPLE2).

Déréférencement avec des variables indirectes

La réponse acceptée pour cette question est:

SAMPLE1='1-first.with.custom.name'
SAMPLE2='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ ))
do
   var="SAMPLE$i"
   echo ${!var}
done

Il s'agit techniquement d'un processus en trois étapes. Tout d'abord, il attribue un nom de variable interpolé à var, puis déréférence le nom de variable stocké dans var, et enfin développe le résultat. Cela semble un peu plus propre, et certaines personnes sont plus à l'aise avec cette syntaxe qu'avec eval, mais le résultat est en grande partie le même.

Itération sur un tableau

Vous pouvez simplifier la boucle et l'expansion en itérant sur un tableau au lieu d'utiliser l'interpolation variable. Par exemple:

SAMPLE=('1-first.with.custom.name' '2-second.with.custom.name')
for i in "${SAMPLE[@]}"; do
    echo "$i"
done

Cela a ajouté des avantages par rapport aux autres méthodes. Plus précisément:

  1. Vous n'avez pas besoin de spécifier un test de boucle complexe.
  2. Vous accédez aux éléments de tableau individuels via la syntaxe $ SAMPLE [$ i] .
  3. Vous pouvez obtenir le nombre total d'éléments avec l'expansion de variable $ {# SAMPLE} .

Équivalence pratique pour l'exemple original

Les trois méthodes fonctionneront pour l'exemple donné dans la question d'origine, mais la solution de tableau offre la flexibilité la plus globale. Choisissez celui qui fonctionne le mieux pour les données dont vous disposez.

18
Todd A. Jacobs

Pas autant que je sache, Ils ont dit @ johnshen64. En outre, vous pouvez résoudre votre problème en utilisant un tableau comme celui-ci:

SAMPLE[1]='1-first.with.custom.name'
SAMPLE[2]='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ )) do
    echo ${SAMPLE[$i]}
done

Notez que vous n'avez pas besoin d'utiliser des nombres comme index SAMPLE[hello] fonctionnera aussi bien

3
Miquel

Vous pouvez utiliser eval comme indiqué ci-dessous:

SAMPLE1='1-first.with.custom.name'
SAMPLE2='2-second.with.custom.name'

for (( i = 1; i <= 2; i++ ))
do
  eval echo \$SAMPLE$i
done
3
dogbane

Pas une réponse autonome, juste un ajout à la réponse de Miquel que je ne pouvais pas bien intégrer dans un commentaire.

Vous pouvez remplir le tableau à l'aide d'une boucle, de l'opérateur + = et d'un document ici également:

SAMPLE=()
while read; do SAMPLE+=("$REPLY"); done <<EOF
1-first.with.custom.name
2-second.with.custom.name
EOF

Dans bash 4.0, c'est aussi simple que

readarray SAMPLE <<EOF
1-first.with.custom.name
2-second.with.custom.name
EOF
2
chepner