Administration Unix - CM séance 06
7. Structures de contrôle du shell (suite)
7.3. Les boucles
7.3.1. La boucle while
La syntaxe prend cette forme :
while cmd1 ; do bloc1 ; done
On peut remplacer les ;
par des retours chariots, par exemple :
while cmd1 ; do
bloc1
done
- Principe :
- (1) la commande cmd1 est exécutée ;
si elle réussit, le bloc1 est exécuté, puis on recommence en (1),
sinon la boucle est terminée.
Il existe une variante :
until cmd2 ; do bloc2 ; done
cette boucle continue tant que la commande cmd2 échoue, et s'arrête lorsqu'elle a réussi. Elle est donc équivalente à
while ! cmd2 ; do bloc2 ; done
Longueur d'une variable
la longueur d'une variable est obtenue par ${#variable}
:
$ ville="Marseille"
$ echo ${#ville}
9
Exemple 1 : boucler sur la longueur d'une variable qui change
Voici un extrait de code avec un while
; tracer avec set -x
.
cri=""
while test ${#cri} -lt 10 ; do
cri="A$cri" # ceci rallonge la variable
done
echo "$cri !"
Saisie au clavier
On peut effectuer une saisie au clavier avec la commande read :
read variable
read
lit une ligne au clavier, puis la stocke dans la variable
.
$ read nom1
Paul # l'utilisateur tape un nom
$ echo "$nom1"
Paul
Exemple 2 : boucler sur une réponse saisie au clavier
Extrait de code avec un while
et un read
:
reponse=""
while test "$reponse" != "oui" ; do
echo -n "Entrez une réponse : "
read reponse
done
echo "Vous avez donné la bonne réponse (oui)."
7.3.2. La boucle for
La syntaxe générale est :
for nom1 in liste1 ; do bloc1 ; done
liste1
est expansé et découpé en mots sur les blancs (espace ou tabulation) ;
la variable nom1
prend successivement la valeur de chaque mot de liste1
, et
chaque fois bloc1
est exécuté.
Exemples de boucles :
-
Sur des mots :
for note in do ré mi ; do echo "Je joue la note $note" done
-
Sur une liste de mots dans une variable :
liste="pomme poire banane" for item in $liste ; do echo "Je mange une $item" done
Attention, ne pas mettre de ""
autour de $liste
pour qu'elle soit découpée.
-
Sur des fichiers existants :
for file in *.txt ; do echo -n "la taille de $file est " wc -c < "$file" done
-
Sur les arguments : faut-il boucler sur
$*
ou$@
?Essais : on donne 3 arguments, le deuxième avec un espace
$ set aa "bb cc" dd $ echo $# 3 $ echo $2 bb cc $ echo $* aa bb cc dd $ echo $@ aa bb cc dd
Le but est donc de faire ici exactement 3 itérations :
$ for s in $* ; do echo "$s" ; done # affiche 4 lignes $ for s in "$*" ; do echo "$s" ; done # 1 $ for s in $@ ; do echo "$s" ; done # 4 $ for s in "$@" ; do echo "$s" ; done # 3, ok
C'est donc la boucle sur "$@"
qui est la bonne forme pour boucler sur des
arguments sans les redécouper :
for arg in "$@" ; do bloc1 ; done
Il existe d'ailleurs un raccourci équivalent (voir : help for
) :
for arg ; do bloc1 ; done
7.3.3. Contrôle de boucles
Dans le bloc d'une boucle (while
, until
, for
) on peut utiliser les mots clés
suivants :
break
sort immédiatement de la bouclebreak n
sort de n boucles imbriquéescontinue
passe immédiatement à l'itération suivantecontinue n
passe à l'itération suivante de la n-ième boucle supérieure
Exemple 1 : recherche du premier mot commençant par un "p"
dans une liste
alcanes="méthane éthane propane butane pentane hexane"
for mot in $alcanes ; do
if [[ "$mot" == p* ]]; then # sans "" à droite autour du motif
echo "$mot"
break
fi
done
Exemple 2 : affichage de la taille des fichiers qui ne sont pas des liens
for file in * ; do
if test -L "$file" ; then continue ; fi
echo -n "la taille de $file est "
wc -c < "$file"
done
7.4. Le branchement case
7.4.1. case sur des mots
La syntaxe est
case mot in
choix1) bloc1 ;; # notez ")" et ";;"
choix2) bloc2 ;;
...
esac
mot
est expansé (il s'agit souvent d'une variable), puis sa valeur est comparée
aux chaînes choix1
, choix2
, etc (dans cet ordre).
Dès que mot
correspond à l'un des choix, le bloc correspondant est exécuté
jusqu'au ;;
(règle du "premier qui gagne").
Ceci est équivalent à :
if test mot = "choix1" ; then
bloc1
elif test mot = "choix2" ; then
bloc2
elif ...
...
fi
Syntaxe alternative : rajouter une (
devant (optionnel, rarement utilisé) :
case mot in
(choix1) bloc1 ;;
(choix2) bloc2 ;;
...
esac
7.4.2. Les motifs de fichiers
L'instruction case
devient très puissante avec l'utilisation des motifs de
fichiers (pattern glob) dans les choix.
Syntaxe des motifs :
*
toute chaîne, y compris la chaîne vide?
un caractère, n'importe lequel[abc]
un caractère dans cet ensemble 'a','b','c'[a-z]
un caractère dans cet intervalle de 'a' à 'z'[a-zA-Z_]
un caractère dans a-z ou A-Z ou '_'[^..]
ou[!..]
un caractère qui n'est pas dans cette liste
Cas particuliers :
- pour inclure
]
on le met en premier :[]..]
- pour inclure
-
on le met en premier ou dernier :[-..]
ou[..-]
- idem pour exclure
-
:[^-..]
ou[^..-]
- pour exclure
^
ou!
on ne les met pas en premier :[^..^!..]
7.4.3. case sur des motifs
La syntaxe du case est identique avec celle des motifs de fichiers ; de plus
- on peut faire un ou entre plusieurs motifs avec
|
; - les espaces ne sont pas pris en compte, ils doivent être matérialisés
avec
" "
ou' '
; - on peut rajouter un cas par défaut À LA FIN avec
*)
.
case mot in
motif1) bloc1 ;;
motif2|motif3) bloc2 ;;
...
*) bloc_defaut ;; # cas par défaut, À LA FIN
esac
Ceci est équivalent à
if [[ mot == motif1 ]]; then
bloc1
elif [[ mot == motif2 || mot == motif3 ]]; then
bloc2
elif ...
...
else
bloc_defaut
fi
Exemple :
echo -n "Démarrer l'installation ? "
read rep
case "$rep" in
[oO] | [oO][uU][iI] )
echo "L'installation va démarrer"
;;
[nN] | [nN][oO][nN] )
echo "Installation abandonnée"
exit 0
;;
*)
echo "Erreur: oui ou non attendu" >&2
exit 1
esac
7.5. Les fonctions
Une fonction est un nom (par exemple ma_fonction
), associé à un morceau de
code (appelé corps de la fonction).
Chaque fois qu'on invoque son nom, le corps de la fonction est exécuté par le shell courant.
7.5.1. Déclaration
Pour déclarer une fonction on écrit :
ma_fonction () # ou : ma_fonction()
{
# corps de la fonction
}
Ensuite, pour appeler (= invoquer ou exécuter) la fonction, on écrit :
ma_fonction
Exemple :
$ afficher_pid() { echo "Le PID est $BASHPID" ;} # déclaration
$ afficher_pid # appel
Le PID est 12467
$ echo $BASHPID
12467 # la fonction est bien exécutée dans le même shell
Il ne faut JAMAIS séparer les ()
, elles signifient : déclaration de fonction.
Il existe des variantes de déclaration (obsolètes) :
FUNCTION ma_fonction { ... ;}
FUNCTION ma_fonction() { ... ;}
7.5.2. Variables globales
Dans un script, toutes les variables sont globales ; donc une fonction peut utiliser et modifier une variable du script :
lire_reponse() {
echo -n "Entrez votre réponse : "
read REP
}
REP=""
lire_reponse
if test "$REP" == "oui" ; then ... ; fi
C'est une pratique qui peut être DANGEREUSE car elle risque de créer des bugs (par exemple, si plusieurs fonctions manipulent cette variable...).
7.5.3. Variables locales
On peut déclarer des variables locales :
local nom[=valeur]...
elles ne seront visibles que de la fonction...
$ i=5
$ a(){ local i=6 ; echo "$i";}
$ a
6
$ echo "$i"
5
... et des fonctions filles, c'est-à-dire appelées par la fonction :
$ i=5
$ a(){ local i=6 ; b ;}
$ b(){ echo "$i" ;}
$ a
6
$ b
5
Cette utilisation dans des fonctions fille peut AUSSI créer des bugs !
Par exemple si dans a()
on oublie local
, ce sera le i
global qui sera
modifié.
Bon usage :
- mettre les variables qui doivent être globales en MAJUSCULES ;
- déclarer systématiquement les variables comme locales et les mettre en minuscules.
7.5.4. Arguments
Les fonctions peuvent recevoir des arguments ; cela fonctionne exactement comme pour les scripts :
$ tata() { echo "J'ai reçu $# arguments ; le 1er est $1" ;}
$ tata foo bar baz
J'ai reçu 3 arguments ; le 1er est foo
Pour rendre le code plus clair, on décrit les paramètres en commentaire après ()
chercher_fichiers() # rep_depart extension
{
find "$1" -name "*.$2" -print 2> /dev/null
}
chercher_fichiers /usr png
Pour rendre le code encore plus clair, on sauvegarde les paramètres dans des variables locales :
chercher_fichiers() # rep_depart extension
{
local rep_depart=$1 extension=$2
find "$rep_depart" -name "*.$extension" -print 2> /dev/null
}
chercher_fichiers /usr png
7.5.5. Divers
Les fonctions peuvent être redirigées, exactement comme pour une commande ou un script :
chercher_fichiers /usr png | grep icon | wc -l
Après l'appel d'une fonction, le code de terminaison $?
est celui de la
dernière commande exécutée.
On peut interrompre une fonction avec return :
return
le code de terminaison est celui de la dernière commande exécutée ;return n
le code de terminaison estn
(0 succès, 1..255 échec).
Comment renvoyer un résultat ? On le verra la prochaine fois, avec la
substitution de commandes $()
.