28. Références indirectes

Nous avons vu que lorsqu'on référence une variable, $var, on obtient sa valeur. Mais que dire de la valeur d'une valeur ? Que penser de $$var ?

En fait, la notation correcte est \$$var, généralement précédée d'un eval (et parfois aussi un echo). Cela s'appelle une référence indirecte.

Exemple 28.1. Références indirectes aux variables

#!/bin/bash
# index-ref.sh : Référencement de variable indirecte.
# Accéder au contenu du contenu d'une variable.

a=lettre_de_l_alphabet # La variable a contient le nom d'une autre variable.
lettre_de_l_alphabet=z

echo

# Référence directe.
echo "a = $a"          # a = lettre_de_l_alphabet

# Référence indirecte.
eval a=\$$a
echo "Maintenant, a = $a" # Maintenant, a = z

echo


# Maintenant, essayons de changer la référence du deuxième.

t=tableau_cellule_3
tableau_cellule_3=24
echo "\"tableau_cellule_3\" = $tableau_cellule_3"  # "tableau_cellule_3" = 24
echo -n "\"t\" déréférencé  = "; eval echo \$$t    # "t" déréférencé  = 24
# Dans ce cas simple, ce qui suit fonctionne aussi (pourquoi ?).
#   eval t=\$$t; echo "\"t\" = $t"

echo

t=tableau_cellule_3
NOUVELLE_VALEUR=387
tableau_cellule_3=$NOUVELLE_VALEUR
echo "Modification de la valeur de \"tableau_cellule_3\" en $NOUVELLE_VALEUR."
echo "\"tableau_cellule_3\" vaut maintenant $tableau_cellule_3"
echo -n "\"t\" déréférencé maintenant "; eval echo \$$t
# "eval" prend deux arguments "echo" et "\$$t" (valeur égale à $tableau_cellule_3)

echo

# (Merci, Stéphane Chazelas, pour la clarification sur le comportement ci-dessus.)


#  Une autre méthode est la notation ${!t}, discutée dans la section
#+ "Bash, version 2".
#  Voir aussi ex78.sh.

exit 0

Quel est l'intérêt pratique des références indirectes aux variables ? Cela donne à Bash une partie de la fonctionnalité des pointeurs de C, par exemple dans la recherche dans des tables. Entre autres applications intéressantes...

Nils Radtke montre comment construire des noms de variables « dynamiques » et comment évaluer leur contenu. Cela peut être utile pour l'intégration de fichiers de configuration.

#!/bin/bash

# ---------------------------------------------
# Ceci pourrait être "récupéré" à partir d'un fichier séparé.
isdnMonFournisseurDistant=172.16.0.100
isdnTonFournisseurDistant=10.0.0.10
isdnServiceInternet="MonFournisseur"
# ---------------------------------------------

netDistant=$(eval "echo \$$(echo isdn${isdnServiceInternet}Distant)")
netDistant=$(eval "echo \$$(echo isdnMonFournisseurDistant)")
netDistant=$(eval "echo \$isdnMonFournisseurDistant")
netDistant=$(eval "echo $isdnMonFournisseurDistant")

echo "$netDistant"    # 172.16.0.100

# ================================================================

#  Ensuite c'est encore meilleur.

#  Considérez l'astuce suivante étant donnée une variable nommée
#+ getSparc, mais sans variable getIa64 :

chkMirrorArchs () {
  arch="$1";
  if [ "$(eval "echo \${$(echo get$(echo -ne $arch |
        sed 's/^\(.\).*/\1/g' | tr 'a-z' 'A-Z'; echo $arch |
        sed 's/^.\(.*\)/\1/g')):-false}")" = true ]
  then
    return 0;
  else
    return 1;
  fi;
}

getSparc="true"
unset getIa64
chkMirrorArchs sparc
echo $?        # 0
               # True

chkMirrorArchs Ia64
echo $?        # 1
               # False

# Notes :
# ------
# Même la partie du nom de la variable à substituer est construite explicitement.
# Les paramètres des appels à chkMirrorArchs sont tous en minuscule.
# Le nom de la variable est composé de deux parties : "get" et "Sparc" . . .

Exemple 28.2. Passer une référence indirecte à awk

#!/bin/bash

# Une autre version du script "column totaler"
# qui ajoute une colonne spécifiée (de nombres) dans le fichier cible.
# Celui-ci utilise les références indirectes.

ARGS=2
E_MAUVAISARGS=65

if [ $# -ne "$ARGS" ] # Vérifie le bon nombre d'arguments sur la ligne de
                      # commande.
then
   echo "Usage: `basename $0` nomfichier numéro_colonne"
   exit $E_MAUVAISARGS
fi

nomfichier=$1
numero_colonne=$2

#===== Identique au script original, jusqu'à ce point =====#


# Un script multi-ligne est appelé par awk ' ..... '


# Début du script awk.
# ------------------------------------------------
awk "

{ total += \$${numero_colonne} # référence indirecte
}
END {
     print total
     }

     " "$nomfichier"
# ------------------------------------------------
# Fin du script awk.

# La référence de variable indirecte évite les problèmes de
# référence d'une variable shell à l'intérieur d'un script embarqué.
# Merci, Stephane Chazelas.


exit 0

[Attention]

Attention

Cette méthode de référence indirecte est un peu délicate. Si la variable de second ordre change de valeur, alors la variable de premier ordre doit être correctement déréférencée (comme sur l'exemple ci-dessus). Fort heureusement, la notation ${!variable} introduite dans la version 2 de Bash (voir l'Exemple 37.2, « Références de variables indirectes - la nouvelle façon » et l'Exemple A.22, « Pour en savoir plus sur les fonctions de hachage ») rend les références indirectes plus intuitives.