Chapitre 10. Un peu plus sur les variables

Table des matières

1. Types de variables
1.1. Affectation générale de valeur.
1.2. Utiliser l'intégrée declare
1.3. Constantes
2. Variables tableau
2.1. Créer des tableaux
2.2. Invoquer les variables d'un tableau
2.3. Supprimer des variables tableau
2.4. Exemples de tableaux
3. Opérations sur les variables
3.1. Arithmétique sur les variables
3.2. Longueur de variable
3.3. Transformation de variables
4. Résumé
5. Exercices

Résumé

Dans ce chapitre, nous aborderons l'emploi plus poussé des variables et paramètres. Une fois achevé, vous serez capable de:

  • Déclarer et utiliser un tableau de variables

  • Spécifier le type de variable que vous voulez utiliser

  • Rendre les variables en lecture seule

  • Employer set pour affecter une valeur à une variable

1. Types de variables

1.1. Affectation générale de valeur.

Comme nous l'avons déjà vu, Bash comprend plusieurs types de variables ou paramètres. Jusqu'à maintenant, nous ne nous sommes pas inquiété du type de variable affectée, de sorte que nos variables pouvaient stocker toute sorte de valeur que nous leur affections. Une simple ligne de commande illustre ceci:

[bob in ~] VARIABLE=12

[bob in ~] echo $VARIABLE
12

[bob in ~] VARIABLE=string

[bob in ~] echo $VARIABLE
string

Il y a des cas où vous voulez éviter ce genre de comportement, par exemple quand vous manipulez des numéros de téléphone et autres codifications. A part les entiers et les variables, vous pourriez aussi vouloir spécifier une variable avec une valeur constante. Ceci est souvent fait au début du script, quand la valeur de la constante est définie. Ensuite, il est seulement fait référence au nom de la variable stockant la constante, de sorte que quand la logique veut que la constante soit changée, cela n'est fait qu'à un endroit. Une variable peut aussi être une série de variables de tout type, appelée tableau de variables (VAR0VAR1, VAR2, ... VARN).

1.2. Utiliser l'intégrée declare

Avec l'instruction declare nous pouvons encadrer l'affectation de valeur à une variable.

La syntaxe de declare est la suivante:

declare OPTION(s) VARIABLE=value

Les options suivantes sont employées pour déterminer le type de donnée de la variable et pour lui assigner des attributs:

Tableau 10.1. Options de l'intégrée declare

Optionsens
-aLa variable est un tableau
-fUtilise uniquement les noms de fonction
-iLa variable sera traitée comme un entier; l'évaluation arithmétique est faite quand une valeur est affectée à la variable (voir Section 4.6, « L'expansion arithmétique »).
-pAffiche les attributs et la valeur de chaque variable. Quand -p est employé, les options supplémentaires sont ignorées.
-rfait que la variable est en lecture seule. A cette variable ne peut alors lui être affecté une autre valeur par une instruction ultérieure, de même qu'elle ne peut être supprimée.
-tDonne à chaque variable l'attribut trace.
-xMarque chaque variable comme exportée pour les commandes suivantes via l'environnement.

L'emploi de + au lieu de - inhibe les attributs. Quand c'est employé dans une fonction, declare créé des variables locales.

L'exemple suivant montre comment l'assignation du type de variable influence la valeur.

[bob in ~] declare -i VARIABLE=12

[bob in ~] VARIABLE=string

[bob in ~] echo $VARIABLE
0

[bob in ~] declare -p VARIABLE
declare -i VARIABLE="0"

Notez que Bash a une option pour déclarer une valeur numérique, mais aucune pour une valeur chaîne. Ceci s'explique puisque, par défaut, si aucune spécification n'est indiquée, une variable peut stocker tout type de donnée:

[bob in ~] OTHERVAR=blah

[bob in ~] declare -p OTHERVAR
declare -- OTHERVAR="blah"

Dès que vous restreignez l'affectation de valeurs à une variable, elle ne peut que contenir ce type de donnée. Les restrictions possibles sont soit entier, constante, ou tableau.

Voir les pages info de Bash pour une aide sur le statut renvoyé.

1.3. Constantes

En Bash, les constantes sont créées en mettant en lecture seule une variable. L'intégré readonly marque chaque variable spécifiée comme non modifiable.. La syntaxe est:

readonly OPTION VARIABLE(s)

La valeur de ces variables ne peut plus être changée par une instruction ultérieure. Si l'option -f est donnée, chaque variable réfère à une fonction Shell; voir Chapitre 11, Fonctions. If -a est spécifié, chaque variable réfère à un tableau de variables. Si aucun argument n'est donné, ou si -p est indiqué, une liste de toute les variables en lecture est affichée. Avec l'option -p le résultat peut être réutilisé comme entrée.

Le statut d'exécution est zéro, à moins qu'une option invalide ait été spécifiée, qu'une des variables ou fonctions n'existe pas, ou que -f ait été fourni en tant que nom de variable au lieu d'un nom de fonction.

[bob in ~] readonly TUX=penguinpower

[bob in ~] TUX=Mickeysoft
bash: TUX: readonly variable

2. Variables tableau

2.1. Créer des tableaux

Un tableau est une variable contenant plusieurs valeurs. Toute variable peut être un tableau. Il n'y a pas de limite maximum à la taille d'un tableau, ni de besoin que les éléments soient indexés ou assignés de façon contiguë. Les tableaux démarre à zéro: le premier élément est donc adressé avec le numéro 0.

Une déclaration indirecte peut se faire avec la syntaxe suivante de déclaration de variable:

ARRAY[INDEXNR]=value

Le INDEXNR est traité comme une expression arithmétique qui doit être évalué comme nombre positif.

Une déclaration explicite d'un tableau est faite avec l'intégrée declare:

declare -a ARRAYNAME

Une déclaration avec un numéro d'index sera aussi acceptée, mais le numéro d'index sera ignoré. Les attributs d'un tableau peuvent être spécifiés avec les intégrées declare et readonly. Les attributs s'appliquent à toutes les variables du tableau; vous ne pouvez avoir des tableaux mitigés.

Les variables de tableau peuvent aussi être créées avec une affectation composée selon ce format:

ARRAY=(value1 value2 ... valueN)

Chaque valeur est alors de la forme [indexnumber=]string. Le numéro d'index est optionnel. Si il est fourni, l'index prend la valeur du numéro; sinon l'index de l'élément affecté est le numéro du dernier index assigné, plus un. Le format est accepté par declare également. Si aucun numéro d'index n'est fourni, l'indexation commence à zéro.

Ajouter des éléments manquants ou supplémentaires à un tableau se fait selon la syntaxe:

ARRAYNAME[indexnumber]=value

Se rappeler que l'intégrée read possède l'option -a qui autorise la lecture et l'affectation de valeurs des éléments d'un tableau.

2.2. Invoquer les variables d'un tableau

Pour invoquer le contenu d'un élément d'un tableau, employez les accolades. C'est nécessaire, comme vous le voyez dans l'exemple suivant, pour échapper à l'interprétation du Shell des opérateurs d'expansion. Si le numéro d'index est @ ou *, tous les éléments du tableau sont référencés.

[bob in ~] ARRAY=(one two three)

[bob in ~] echo ${ARRAY[*]}
one two three

[bob in ~] echo $ARRAY[*]
one[*]

[bob in ~] echo ${ARRAY[2]}
three

[bob in ~] ARRAY[3]=four

[bob in ~] echo ${ARRAY[*]}
one two three four

Se référer au contenu d'un élément de tableau sans indiquer le numéro d'index est équivalent à se référer au contenu du premier élément, celui d'index zéro.

2.3. Supprimer des variables tableau

L'intégrée unset est employée pour détruire un tableau ou des éléments d'un tableau:

[bob in ~] unset ARRAY[1]

[bob in ~] echo ${ARRAY[*]}
one three four

[bob in ~] unset ARRAY

[bob in ~] echo ${ARRAY[*]}
<--no output-->

2.4. Exemples de tableaux

Les exemples pratiques d'utilisation de tableaux sont difficiles à trouver. Vous trouverez plein de scripts qui ne font pas autre chose sur votre système que de calculer des séries mathématiques avec des tableaux, par exemple. Et ça devrait être l'un des exemples les plus intéressants...la plupart des scripts ne font que montrer d'une façon hyper simplifiée et théorique ce que vous pouvez faire avec les tableaux.

La raison de cette fadeur tient en ce que les tableaux sont des structures plutôt complexes. Vous verrez que les exemples les plus pratiques pour lesquels les tableaux peuvent être utilisés sont déjà mis en oeuvre sur votre système, mais à plus bas niveau, en langage C dans lequel la plupart des commandes UNIX sont écrites. Un bon exemple est la commande intégrée Bash history. Les lecteurs intéressés peuvent voir le répertoire built-ins dans l'arbre des sources Bash et jeter un coup d'oeil à fc.def, qui est exécutée à la compilation des intégrées.

Une autre raison pour laquelle de bons exemples sont difficiles à trouver est que tous les Shell ne reconnaissent pas les tableaux, ce qui gêne la compatibilité.

Après des jours de recherche, j'ai finalement trouvé cet exemple qui s'exécute chez un fournisseur INTERNET. Il distribue des fichiers de configuration de serveur WEB Apache sur des hôtes dans une ferme WEB:

#!/bin/bash

# $Id: chap10.xml,v 1.8 2005/09/05 12:39:22 tille Exp $
# $Log: chap10.xml,v $
# Revision 1.8  2005/09/05 12:39:22  tille
# sorry made a mistake
#
# Revision 1.6  2005/03/01 19:39:20  tille
# removed blank tracer images, added info on future debugging features, more keywords, minor corrections.
#
# Revision 1.5  2004/12/06 12:27:09  tille
# changes for new domainname, minor corrections
#
# Revision 1.6  2004/10/18 18:58:06  tille
# debugging, typos removed, replaced screenshots in chap9 with screen sections.
#
# Revision 1.5  2004/06/24 14:02:48  tille
# dded tracer image
#
# Revision 1.4  2004/06/15 08:47:12  tille
# more markup, index
#
# Revision 1.3  2004/05/22 13:34:18  tille
# review for fultus
#
# Revision 1.2  2004/04/26 13:24:41  tille
# updates by tabatha
#
# Revision 1.1.1.1  2004/02/11 16:59:50  tille
# initiele bash import
#
# Revision 1.3  2003/02/05 09:52:53  mbounine
# httpd restarting added.
#
# Revision 1.2  2003/02/05 08:11:32  mbounine
# Bug fixes.
#
# Revision 1.1  2003/02/04 15:41:35  mbounine
# Script for syncing httpd config between web farm hosts.
# Initial release.
#
if [ $(whoami) != 'root' ]; then
        echo "Must be root to run $0"
        exit 1;
fi
if [ -z $1 ]; then
        echo "Usage: $0 </path/to/httpd.conf>"
        exit 1
fi

httpd_conf_new=$1
httpd_conf_path="/usr/local/apache/conf"
login=htuser

farm_hosts=(web03 web04 web05 web06 web07)

for i in ${farm_hosts[@]}; do
        su $login -c "scp $httpd_conf_new ${i}:${httpd_conf_path}"
        su $login -c "ssh $i sudo /usr/local/apache/bin/apachectl graceful"

done
exit 0

Les 2 premiers tests sont effectués pour vérifier que le bon utilisateur a demandé l'exécution du script avec les bons paramètres. Les noms des hôtes qui doivent être configurés sont listés dans le tableau farm_hosts. Puis tous ces hôtes sont chargés avec le fichier de configuration Apache, après quoi le démon est redémarré. Notez l'emploi de commandes de la suite Secure Shell, qui encryptent les connections aux hôtes distants.

Merci, Eugène et ses collègues, pour cette contribution.

Dan Richter a fourni l'exemple suivant. Voici le problème auquel il était confronté:

« ...Dans mon entreprise, nous avons des démonstrations sur notre site WEB, et chaque semaine quelqu'un est chargé de les tester toutes. Donc j'ai un travail cron qui remplit un tableau avec les candidats possibles, qui utilise date +%W pour déterminer la semaine dans l'année, et fait une opération modulo pour trouver le bon index. La personne gâtée reçoit un courriel. »

Et voici la solution:

#!/bin/bash
# Ceci est le script: get-tester-address.sh 
#
# D'abord nous testons si Bash admet les tableaux..
# (Les tableaux ont été ajoutés récemment.)
#
whotest[0]='test' || (echo 'Echec: les tableaux ne sont pas admis dans cette version de Bash.' && exit 2)
                                                                                
#
# Our list of candidates. (Vous êtes libre d'ajouter
# ou d'enlever des candidats.)
#
wholist=(
     'Bob Smith <bob@example.com>'
     'Jane L. Williams <jane@example.com>'
     'Eric S. Raymond <esr@example.com>'
     'Larry Wall <wall@example.com>'
     'Linus Torvalds <linus@example.com>'
   )
#
# Compte le nombre de testeurs candidats.
# (Boucle jusqu'à trouver une chaîne vide.)
#
count=0
while [ "x${wholist[count]}" != "x" ]
do
   count=$(( $count + 1 ))
done
                                                                                
#
# Maintenant nous calculons à qui c'est le tour.
#
week=`date '+%W'`    	# La semaine dans l'année (0..53).
week=${week#0}       	# Elimine de possible zéro au début.
                                                                                
let "index = $week % $count"   # week modulo count = la personne gâtée

email=${wholist[index]}     # Récupérer l'adresse email de cette personne.
                                                                                
echo $email     	# Affiche l'adresse email.

Ce script est alors appelé dans d'autres scripts, tel que celui-ci, qui utilise un document intégré:

email=`get-tester-address.sh`   # Trouver à qui envoyer le courriel.
hostname=`hostname`    		# Le nom de la machine locale.
                                                                                
#
# Envoyer le courriel à la bonne personne.
#
mail $email -s '[Demo Testing]' <<EOF
La personne gâtée de la semaine est: $email
                                                                                
Rappel: la liste de démonstrations est ici:
    http://web.example.com:8080/DemoSites
                                                                                
(Ce courriel a été généré par $0 depuis ${hostname}.)
EOF

3. Opérations sur les variables

3.1. Arithmétique sur les variables

Nous avons déjà abordé la question à la Section 4.6, « L'expansion arithmétique ».

3.2. Longueur de variable

La syntaxe ${#VAR} calcul le nombre de caractères d'une variable. Si VAR est « * » ou « @ », cette valeur est remplacée par le nombre paramètres positionnels ou le nombre d'éléments d'un tableau dans le cas général. En voici une démonstration ci-dessous:

[bob in ~] echo $SHELL
/bin/bash

[bob in ~] echo ${#SHELL}
9

[bob in ~] ARRAY=(one two three)

[bob in ~] echo ${#ARRAY}
3

3.3. Transformation de variables

3.3.1. Substitution

${VAR:-WORD}

Si VAR n'est pas défini ou est nul, l'expansion de WORD est employée; sinon la valeur de VAR est remplacée:

[bob in ~] echo ${TEST:-test}
test

[bob in ~] echo $TEST
 

[bob in ~] export TEST=a_string

[bob in ~] echo ${TEST:-test}
a_string

[bob in ~] echo ${TEST2:-$TEST}
a_string

Cette forme est souvent employée dans les tests conditionnels, par exemple dans celui-ci:

[ -z "${COLUMNS:-}" ] && COLUMNS=80

C'est une notation plus courte pour

if [ -z "${COLUMNS:-}" ]; then
	COLUMNS=80
fi

Voir la Section 1.2.3, « Comparaisons de chaînes » pour plus de détails au sujet de ce type de test de condition.

Si le tiret (-) est remplacé par le signe égal (=), la valeur est affectée au paramètre si il n'existe pas:

[bob in ~] echo $TEST2


[bob in ~] echo ${TEST2:=$TEST}
a_string

[bob in ~] echo $TEST2
a_string

La syntaxe suivante teste l'existence d'une variable. Si elle n'est pas déclarée, l'expansion de WORD est affichée sur le standard de résultat et un Shell non-interactif se termine. Une démonstration:

[bob in ~] cat vartest.sh
#!/bin/bash
 
# Ce script teste si une variable est déclarée.  Si non,
# Il quitte en affichant un message.
 
echo ${TESTVAR:?"Il y a tellement encore que je voudrais faire..."}
echo "TESTVAR est déclarée, nous pouvons traiter."

[bob in testdir] ./vartest.sh
./vartest.sh: line 6: TESTVAR: Il y a tellement encore que je voudrais faire...

[bob in testdir] export TESTVAR=present

[bob in testdir] ./vartest.sh
present
TESTVAR est déclarée, nous pouvons traiter.

Avec « + » au lieu du point d'exclamation la variable prend la valeur de l'expansion de WORD; si elle n'existe pas, rien ne se produit.

3.3.2. Suppression de sous-chaînes

Pour éliminer d'une variable un nombre de caractères égal à OFFSET, la syntaxe à employer est:

${VAR:OFFSET:LENGTH}

Le paramètre LENGTH définit combien de caractères garder, à partir du premier caractère après le décalage. Si LENGTH est omis, le reste du contenu de la variable est conservé:

[bob in ~] export STRING="thisisaverylongname"

[bob in ~] echo ${STRING:4}
isaverylongname

[bob in ~] echo ${STRING:6:5}
avery

${VAR#WORD}

et

${VAR##WORD}

Cette syntaxe est employée pour éliminer les correspondances du patron donné par l'expansion de WORD de VAR. WORD est interprété pour donner un patron tout comme dans l'expansion de nom de fichier. Si le patron correspond au début du résultat d'expansion de VAR, alors le résultat est la valeur de VAR réduit au plus court patron correspondant (« # ») ou le plus long (quand employé avec « ## »).

Si VAR est * ou @, l'opération de suppression du patron est effectuée sur chaque paramètre positionnel, et l'expansion est la liste résultante.

Si VAR est une variable tableau indexé par « * » ou « @ », l'opération de suppression du patron est effectuée sur chaque élément, et l'expansion est la liste résultante. Ceci est montré dans l'exemple ci-dessous:

[bob in ~] echo ${ARRAY[*]}
one two one three one four

[bob in ~] echo ${ARRAY[*]#one}
two three four

[bob in ~] echo ${ARRAY[*]#t}
one wo one hree one four

[bob in ~] echo ${ARRAY[*]#t*}
one wo one hree one four

[bob in ~] echo ${ARRAY[*]##t*}
one one one four

L'effet opposé est obtenu avec « % » et « %% », comme dans l'exemple suivant. WORD devrait correspondre à une portion en fin de chaîne:

[bob in ~] echo $STRING
thisisaverylongname

[bob in ~] echo ${STRING%name}
thisisaverylong

3.3.3. Remplacer des parties de noms de variables

Ceci est fait avec la syntaxe suivante

${VAR/PATRON/CHAINE}

ou

${VAR//PATRON/CHAINE}

syntax. La première forme remplace seulement la première correspondance, la seconde remplace toutes les occurrences de PATRON par CHAINE:

[bob in ~] echo ${STRING/name/string}
thisisaverylongstring

Vous trouverez plus de détails dans les pages info de Bash.

4. Résumé

Normalement, une variable peut stocker tout type de donnée, à moins qu'elles soient déclarées explicitement. Les variables constantes sont déclarées avec la commande intégrée readonly.

Un tableau stocke un ensemble de variables. Si un type de donnée est déclaré, alors tous les éléments du tableau seront considérés comme contenant seulement ce type de donnée.

Les fonctionnalités du Bash permettent la substitution et la transformation de variables « au vol ». Les opérations standards incluent le calcul de la longueur de la variable, l'arithmétique de variables, la substitution du contenu - ou d'une partie - d'une variable.

5. Exercices

Voici quelques casse-têtes:

  1. Ecrire un script qui fait ce qui suit:

    • Affiche le nom du script qui s'exécute.

    • Afficher le premier, le troisième et le dixième paramètre donné au script.

    • Afficher le nombre total de paramètres du script.

    • Si il y a plus de 3 paramètres positionnels, employez shift pour décaler toutes les valeurs de 3 places vers la gauche.

    • Afficher toutes les valeurs des paramètres restants.

    • Affiche le nombre de paramètres.

    Tester avec zéro, un, trois et plus de dix paramètres.

  2. Ecrire un script qui installe un navigateur WEB simple (en mode texte), avec wget et links -dump pour afficher les pages HTML à l'intention de l'usager. L'usager a 3 choix: entrer une URL,entrer b pour boucler, q pour quitter. Les 10 dernières URL entrées par l'usager sont stockées dans un tableau, duquel l'usager peut récupérer une URL avec la fonctionnalité boucler.