La commande interne while correspond à l’itération tant que présente dans de nombreux langages de programmation.
Syntaxe : while suite_cmd1
do
suite_cmd2
done
La suite de commandes suite_cmd1 est exécutée; si son code de retour est égal à 0, alors la suite de commandes suite_cmd2 est exécutée, puis suite_cmd1 est réexécutée. Si son code de retour est différent de 0, alors l’itération se termine.
En d’autres termes, suite_cmd2 est exécutée autant de fois que le code de retour de suite_cmd1 est égal à 0.
L’originalité de cette structure de contrôle est que le test ne porte pas sur une condition booléenne (vraie ou fausse) mais sur le code de retour issu de l’exécution d’une suite de commandes.
En tant que mots-clé, while, do et done doivent être les premiers mots d'une commande.
Une commande while, comme toute commande interne, peut être écrite directement sur la ligne de commande.
$ > aa # creation du fichier aa $ $ while ls aa &>/dev/null > do > echo ″le fichier aa est present″ > sleep 5 > done le fichier aa est présent le fichier aa est présent … $
Le fonctionnement est le suivant : la commande ls aa est exécutée. Son code de retour sera égal à 0 si le fichier aa est présent dans le répertoire courant. Dans ce cas, la ligne correspondant à ce fichier sera affichée sur la sortie standard. Comme seul le code de retour est intéressant et non le résultat, la sortie standard de ls est redirigée vers le puits (/dev/null). Enfin, le message est affiché et le programme « s’endort » pendant 5 secondes. Ensuite, la commande ls aa est réexécutée et son code de retour est retesté.
Si l'utilisateur ouvre un autre terminal et y exécute la commande rm aa, le code de retour de la commande ls aa devient différent de 0 et l'itération se termine (le message d’erreur produit par la commande ls ne sera pas affiché car la sortie standard pour les messages d’erreur est également redirigée vers le puits).
Commandes internes while et deux-points
La commande interne deux-points associée à une itération while compose rapidement un serveur (démon) rudimentaire.
$ while : => boucle infinie > do > ps --user root > fich => traitement à effectuer > sleep 300 => temporisation > done & [1] 12568 => pour arrêter l’exécution : kill 12568 $
Lecture du contenu d’un fichier texte
La commande interne while est parfois utilisée pour lire le contenu d’un fichier texte. La lecture s’effectue alors ligne par ligne. Il suffit pour cela :
de placer une commande interne read dans suite_cmd1
de placer les commandes de traitement de la ligne courante dans suite_cmd2
de rediriger l’entrée standard de la commande while avec le fichier à lire.
Syntaxe : while read [ var1 ... ]
do
commande(s) de traitement de la ligne courante
done < fichier_à_lire
Exemple : programme shell psroot qui affiche les noms des processus s'exécutant dans le système et appartenant à l'administrateur système (root)
À cette fin, on utilise la commande ps --no-headers –user root.
$ ps --no-headers --user root 1 ? 00:00:09 systemd 2 ? 00:00:00 kthreadd 3 ? 00:00:00 rcu_gp 4 ? 00:00:00 rcu_par_gp . . . $
Le contenu du programme shell psroot est le suivant :
#!/bin/bash # @(#) psroot ps -–no-headers –-user root > tmp while read x y z nom do echo $nom done < tmp rm tmp
L'option --no-headers de la commande ps évite l'affichage de la ligne indiquant l'intitulé des colonnes.
Les variables x, y et z permettent de capter les valeurs des champs qui ne nous intéressent pas.
L’exécution du programme psroot affiche les résultats suivants :
$ psroot systemd kthreadd rcu_gp . . . $
Lorsque le fichier à lire est créé par une commande cmd, comme dans le programme psroot, on peut utiliser la syntaxe :
cmd | while read [ var1 ... ]
do
commande(s) de traitement de la ligne courante
done
Exemple : programme shell psroot1
#!/bin/bash # @(#) psroot1 ps --no-headers --user root | while read x y z nom do echo $nom done
On peut également utiliser une substitution de processus [cf. Chapitre 5, Redirections élémentaires, §4].
Exemple : programme shell psroot2
#!/bin/bash # @(#) psroot2 while read x y z nom do echo $nom done < <( ps --no-headers --user root )
Par rapport au programme shell psroot, les programmes shell psroot1 et psroot2 ne gèrent pas de fichier temporaire tmp.
Remarques sur la lecture d'un fichier avec while
La lecture ligne par ligne d’un fichier à l’aide d’une commande interne while est lente et peu élégante. Il est préférable d’utiliser une suite de filtres permettant d’aboutir au résultat voulu. Par exemple, en utilisant un filtre sed on peut s'affranchir de l'itération while.
Exemple : programme psroot.sed
#!/bin/bash # @(#) psroot.sed ps --no-headers --user root | sed -e's/.* //'
La commande unix sed permet de modifier les lignes lues suivant un ou plusieurs modèles de transformation. Dans le programme shell psroot.sed, la commande s de sed indique de remplacer la plus longue sous-chaine (.*) se terminant par un caractère espace (/.* /) par rien (//) dans chaque ligne lue. En d’autres termes, elle supprime tous les caractères à partir du début de la ligne jusqu’au dernier caractère espace inclus.
$ ps --no-headers --user root 1 ? 00:00:11 systemd 2 ? 00:00:00 kthreadd 3 ? 00:00:00 rcu_gp . . . $ $ psroot.sed systemd kthreadd rcu_gp . . . $
Lorsque le traitement à effectuer est plus complexe, il est préférable d’utiliser une commande spécialisée comme awk.
Une deuxième raison d’éviter la lecture ligne à ligne d’un fichier avec while est qu’elle peut conduire à des résultats différents suivant le mode de lecture choisi (tube, simple redirection ou substitution de processus).
Prenons l’exemple où l’on souhaite mettre à jour la valeur d’une variable var après lecture de la première ligne d’un fichier.
Le programme shell maj ci-dessous connecte à l’aide d’un tube la sortie standard de la commande unix ls à l’entrée de la commande interne while. Après lecture de la première ligne, la valeur de la variable var est modifiée puis l'itération se termine (commande interne break). Pourtant, cette nouvelle valeur ne sera pas affichée. En effet, l’utilisation du tube a pour effet de faire exécuter l’itération while par un processus différent : il y a alors deux instances différentes de la variable var : celle qui a été initialisée à 0 au début de l’exécution de maj et celle interne au nouveau processus qui initialise à 1 sa propre instance de var. Après terminaison de l’itération while, le processus qui l’exécutait disparaît ainsi que sa variable var initialisée à 1 [cf. Chapitre 5, Redirections élémentaires, § 3].
Exemple : programme maj
#!/bin/bash # @(#) maj var=0 ls | while read do var=1 break done echo $var
$ maj 0 $
Pour résoudre ce problème, il suffit d’utiliser une substitution de processus [cf. Chapitre 5, Redirections élémentaires, § 4] à la place d’un tube.
Exemple : programme maj1
#!/bin/bash # @(#) maj1 var=0 while read do var=1 break done < <(ls) echo $var
$ maj1 1 $
En utilisant la substitution de processus <(ls), le shell crée un fichier temporaire dans lequel sont écrites les données produites par l’exécution de la commande unix ls. Le contenu de ce fichier alimente ensuite l’entrée standard de la commande interne while à l’aide d’une redirection <.
Lorsque l’on exécute le programme maj1, bash ne crée pas un nouveau processus pour exécuter l’itération while : il n’y a qu’une instance de la variable var qui est convenablement mise à jour.
On ne doit pas utiliser le couple de commandes internes while read pour lire le contenu d’un fichier binaire.