PROFDINFO.COM

Votre enseignant d'informatique en ligne

Section 4 - Les boucles

Retour à la page du cours

Cette section couvrira les différents types de boucles en bash. Les boucles while et until ne vous apporteront aucune surprise. Toutefois, le for fonctionne de façon différente de VB (et ouvre plusieurs possibilités intéressantes) et le select est totalement nouveau.

4.1 - select: une boucle de menu

4.2 - Les boucles "classiques": while et until

4.3 - Les boucles for

4.4 - Quelques petits exercices tout simples

4.1 - select: une boucle de menu

Il existe un moyen bien simple d'offrir un menu à l'usager et lui demander d'en choisir un des items. La commande select permet de réaliser ces opérations efficacement et fonctionne en plus en boucle: l'usager sera ramené automatiquement au menu après chaque sélection.

Voici la syntaxe de la commande:

select <variable> in <choix>
do
   # On placera ici une alternative qui compare le contenu
   # de la <variable> avec les différents choix
   if [ $<variable> == <choix 1> ]; then
       # commandes à effectuer
   elif [ $<variable> == <choix 2> ]; then
       # commandes à effectuer
	# et ainsi de suite
   fi
done

La <variable> est simplement le nom d'une variable quelconque qu'on passe au select. Le choix que l'usager fera sera placé dans cette variable pour fins de comparaisons.

Le <choix> est une liste de choix séparés par des espaces. Ce sont les choix qui seront donnés à l'usager. Notez que si un des noms de choix contient lui-même un ou des espaces, il devra être placé entre guillemets.

Notez que le do doit être placé sur la ligne en-dessous du select. Tout comme le then avec le if, on peut le placer sur la même ligne si on met un point-virgule entre les deux:

select <variable> in <choix>; do

Voici un exemple plus concret:

select CHOIX in Salutations Compliment Insulte; do

Ceci provoquera l'apparition du menu suivant à l'écran:

   1) Salutations
   2) Compliment
   3) Insulte
   #?

Le programme attendra ensuite une entrée de la part de l'usager. Remarquez que chaque choix se retrouve automatiquement numéroté. C'est le numéro que l'usager devra entrer, mais c'est le mot qui se retrouvera dans la variable CHOIX après coup. Autrement dit, dans le script, les numéros n'existent pas: on ne les écrit pas et on ne les utilise pas dans les comparaisons. Ils ne sont là que pour l'usager.

Ce qui est intéressant, c'est que si l'usager entre un choix invalide (un nombre trop petit ou trop grand, ou même du texte), le choix est ignoré et l'invite est réaffichée. Si l'usager n'entre rien du tout (fait simplement un Enter), le menu est réaffiché au complet. Toute cette gestion est faite automatiquement pour nous. (En réalité, si l'usager entre un choix invalide, la variable contiendra NULL (aucune valeur) et le corps du select sera parcouru. Comme on ne fait rien pour réagir à cette possibilité, l'exécution atteindra le done et remontera au select pour un autre tour.)

Notez que l'invite "#?" est en fait le contenu de la variable PS3 (pour Prompt String 3 et non PlayStation 3 - le prompt string 1 est l'invite que vous voyez avant d'entrer vos commandes dans le shell et le prompt string 2 est le simple ">" qui est affiché si vous entrez une commande qui n'est pas terminée). Si vous voulez la changer, ajoutez simplement une ligne avant le select et donnez une nouvelle valeur à PS3.

Il ne nous reste plus qu'à vérifier le contenu de CHOIX et d'agir en conséquence. Notre boucle complète sera donc:

select CHOIX in Salutations Compliment Insulte; do
   if [ "$CHOIX" == "Salutations" ]; then
      echo "Bonjour, la, bonjour!"
   elif [ "$CHOIX" == "Compliment" ]; then
      echo "Que vous etes joli!  Que vous me semblez beau!"
   elif [ "$CHOIX" == "Insulte" ]; then
      echo "Ton impudence, temeraire vieillard, aura sa recompense!"
   fi
done

(je n'utilise pas d'accents pour être certain que cet exemple, une fois copié et collé sur un système Linux anglophone fonctionnera sans problèmes.)

Notez que j'ai, par habitude, mis des guillemets autour des variables et des valeurs lors des comparaisons. Ceci n'est pas nécessaire, à moins qu'un des choix contienne un espace. Selon moi, mieux vaut prendre l'habitude de les utiliser tout le temps pour éviter un oubli.

Comme le select répète son menu dès qu'un choix invalide est fait par l'usager, nous n'avons pas besoin de gérer ce cas d'exception.

Si vous avez expérimenté un peu avec le code ci-haut, vous aurez vite réalisé qu'on se retrouve dans une boucle infinie. Pour pouvoir en sortir, notre seul choix est de tuer le processus avec CTRL-C - pas très élégant. C'est pourquoi il existe la commande break, qui nous sort de la boucle en cours et nous place donc sur la ligne juste en-dessous du done. On pourrait alors ajouter une option à notre menu:

select CHOIX in Salutations Compliment Insulte "Arreter le script"; do
   if [ "$CHOIX" == "Salutations" ]; then
      echo "Bonjour, la, bonjour!"
   elif [ "$CHOIX" == "Compliment" ]; then
      echo "Que vous etes joli!  Que vous me semblez beau!"
   elif [ "$CHOIX" == "Insulte" ]; then
      echo "Ton impudence, temeraire vieillard, aura sa recompense!"
   elif [ "$CHOIX" == "Arreter le script" ]; then
      break
   fi
done
echo "Merci d'avoir joue avec moi"

On peut, bien entendu, utiliser plus d'un break dans notre select, pour permettre à plusieurs choix de mettre fin à la boucle après leurs opérations. On pourrait aussi, dans ce cas-là, utiliser carrément un exit pour quitter le script, ce qui reviendrait au même puisqu'on ne fait rien après la boucle (à part afficher un message qu'on pourrait afficher avant de faire exit).

Retour à la table des matières de la section

4.2 - Les boucles "classiques": while et until

La syntaxe d'une boucle while est la suivante:

while <condition>
do
   <commandes>
done

Comme d'habitude, on peut mettre le do sur la même ligne que le while en le précédant d'un point-virgule.

La <condition> peut être n'importe quel test. On la placera entre crochets, de la même façon que dans un if.  On pourra utiliser les –eq, -ne, etc pour tester des valeurs numériques, et des ==, !=, etc pour tester des valeurs textuelles, en plus des -f et -d pour tester l'existence de fichiers. En fait, tout ce que l'on pourrait utiliser dans un if peut être utilisé dans un while.

La première fois, si la condition est VRAIE, la boucle est exécutée.  Une fois rendu au done, la condition est revérifiée.  Si elle est encore VRAIE, on recommence.  Sinon, on continue l'exécution du script après le done.

Il n'y a aucune surprise ici, ça fonctionne exactement comme une boucle while en VB.

Par exemple, essayez ce script:

read TEXT
while [ "$TEXT" != "bye" ]
do
   echo "Tu as dit $TEXT!"
   read TEXT
done
echo "Ok bye!"

Il commence par lire du texte de l'usager.  Il vérifie ensuite si le texte est différent de "bye".  Si c'est vrai qu'il est différent, il répète le texte entré, puis il lit une nouvelle phrase de l'usager.  Si la condition est fausse (donc si l'usager a bel et bien écrit "bye"), on sort de la boucle et les mots "Ok bye!" sont affichés. Des heures de plaisir.

Retour à la table des matières de la section

4.2.1 - Arithmétique et utilisation de compteurs

Si en bash les variables ne sont pas typées, peut-on tout de même les utiliser pour compter? Par exemple, j'essaie ceci:

VAR=10
VAR=$VAR+1
echo $VAR       

Intuitivement, ça semblerait fonctionnel. Toutefois, le résultat affiché est simplement:

10+1

Voilà qui ne nous aide pas beaucoup. En effet, en y repensant, $VAR est remplacé par sa valeur. La ligne VAR=$VAR+1 devient donc VAR=10+1 et c'est 10+1 qui sera stocké dans la variable. Aucune façon pour bash de deviner que ce que vous voulez stocker n'est pas le texte "10+1" mais le résultat de l'opération arithmétique.

Il y a toutefois une façon d'y arriver: l'utilisation de la commande let. Les anciens programmeurs basic parmi vous se rappelleront peut-être qu'à l'époque, toutes les assignations de variables demandaient l'utilisation de "let".

En bash, on n'utilisera let que lorsque l'on veut assigner le résultat d'une opération arithmétique à une variable. Notre exemple deviendrait donc:

VAR=10
let VAR=VAR+1
echo $VAR 

Cette fois-ci, on obtient bel et bien 11. Notez que lorsqu'on utilise let, les $ avant les noms des variables desquelles extraire une valeur deviennent facultatifs.

Muni de cette commande, on pourrait donc faire une très typique boucle qui compte en bash:

VAR=0
LIMITE=10

while [ $VAR –lt $LIMITE ]; do
   echo $VAR
   let VAR=VAR+1
done

La variable VAR est d'abord initialisée à 0.  Puis, tant que sa valeur est plus petite que la limite (10), VAR sera affichée à l'écran, puis incrémentée de 1.  Éventuellement, à force d'être augmentée, elle finira par atteindre la limite et on sortira de la boucle.

Retour à la table des matières de la section

4.2.2 - Les boucles until

Les boucles until fonctionnent exactement comme des boucles while, sauf qu'elles tournent jusqu'à ce que quelque chose deviennent vrai, au lieu de tourner tant que quelque chose est vrai. Encore une fois, c'est le même principe qu'en VB.

Revoici notre boucle de perroquet, faite avec un until:

read TEXT
until [ "$TEXT" == "bye" ]; do
   echo "Tu as dit $TEXT!"
   read TEXT
done
echo "Ok bye!"

La seule ligne qui change est la deuxième, on change while pour until et on inverse la condition.

Retour à la table des matières de la section

4.2.3 - Des commandes comme conditions

Il est possible, et c'est une fonctionnalité assez puissante, d'utiliser une commande en guise de condition (c'est bon pour une boucle, mais c'est aussi bon pour une alternative). À ce moment-là, la commande est exécutée et son code de retour est vérifié. Un code de retour de 0 (pas d'erreur) signifie "vrai" et tout autre nombre (erreur) signifie "faux". Cette valeur booléenne sera utilisée pour déterminer si on doit faire un autre tour de boucle ou non, mais peu importe, la commande aura tout de même été exécutée une fois.

C'est assez utile pour répéter une commande jusqu'à ce qu'elle fonctionne.

Voici un exemple fort intéressant:

echo "A la recherche de $1"      
until who | grep -q $1      
do         
   sleep 10      
done      
echo "!!! ATTENTION !!!" 
echo "$1 vient de se brancher!" 

Ce programme est fait pour être exécuté en arrière-plan (avec le &).  Essayez-le et tentez de deviner ce qu'il fait et comment il s'y prend!

Ce qu'il faudrait savoir (ou se rappeler):

  • Que fait who?
  • Que fait grep?
  • Que fait l'option -q de grep?
  • Que fait sleep (on peut sans doute le déduire!)
  • Quelle commande envoie le code de retour à until?
  • À quoi sert le paramètre?

Retour à la table des matières de la section

4.3 - Les boucles for

Il existe une boucle for en bash, mais elle fonctionne d'une façon un peu différente de ce que vous avez vu en VB.

La syntaxe d'une boucle "for" est la suivante:

for <variable> in <liste>
do
   <commandes>
done

La <variable> est le nom d'une variable quelconque.  Cette variable recevra à chaque itération de la boucle une valeur de la liste, dans l'ordre.

La <liste> est une liste de valeurs quelqconques, séparées par des espaces.

Les <commandes> sont des commandes qui seront exécutées à chaque tour de boucle.  On utilisera normalement la <variable> quelque part dans ces commandes.

Voici un exemple simple d'une boucle for:

for LEGUME in  patates carottes navets "choux de Bruxelles"
do
   echo "C'est bon les $LEGUME.  Miam miam"
done

Dans cet exemple, la variable LEGUME prendra tour à tour les valeurs "patates", "carottes", "navets" et "choux de Bruxelles" (remarquez en passant l'utilisation des guillemets autour de "choux de Bruxelles" – comme les valeurs de la liste sont séparées par des espaces, les guillemets nous permettent d'utiliser une valeur composée de plusieurs mots).

La commande echo sera ensuite exécutée pour chaque valeur de LEGUME.  Notez qu'on fait référence à la variable comme d'habitude, avec $LEGUME pour voir son contenu.  C'est une variable comme les autres, la seule différence c'est qu'au lieu de lui assigner directement une valeur, on laissera le "for" lui en assigner une pour nous.

On pourrait faire une boucle qui compte, un peu comme on l'a fait avec while et let, de cette façon:

for VAR in 0 1 2 3 4 5 6 7 8 9
do
   echo $VAR
done

Ça va bien si on va jusqu'à 9, mais c'est moins intéressant si on doit se rendre jusqu'à 500. La boucle for en bash est donc rarement utilisée pour compter, contrairement à ce qu'on fait en VB.

Retour à la table des matières de la section

4.3.1 - Les apostrophes inversées et for

Comme n'importe où ailleurs dans bash, on peut utiliser les apostrophes inversées pour utiliser le résultat d'une commande en guise liste.  L'utilisation la plus courante de cette fonctionnalité dans un "for" est le "ls".  Ceci nous permet d'obtenir tour à tour le nom de chaque fichier dans le répertoire courant et d'en faire quelque chose.

for FILE in `ls`
do
   echo "Je vois le fichier $FILE!"
done

Les apostrophes inversées, rappelons-le, permettent d'exécuter d'abord la commande qu'elles contiennent et de la remplacer par sa sortie (ou son résultat).

Dans ce cas-ci, le résultat du ls, qui n'est rien d'autre que les noms des fichiers du répertoire courant séparés par des espaces, est utilisé comme liste dans le for, donc la variable FILE contiendra tour à tour chacun de ces noms de fichiers.

Évidemment, c'est toujours possible d'utiliser un paramètre pour restreindre le ls:


for FILE in `ls $1*`
do
   echo "Je vois le fichier $FILE!"
done

De cette façon, l'usager pourra appeler le script en faisant:

script a 

À ce moment, seuls les fichiers qui commencent par "a" seront listés. 

Retour à la table des matières de la section

4.3.2 - Utilisation des paramètres comme liste

Nous avons déjà vu plusieurs "variables spéciales" en bash:

  • $1, $2, ...  donnent les paramètres
  • $0 donne le nom du script
  • $# donne le nombre de paramètres reçus
  • $? donne le code de retour de la dernière commande

Il existe aussi le fort utile $*, qui donne la liste de tous les paramètres telle qu'elle a été entrée par l'usager.  Par exemple, si l'usager appelle notre script en faisant:

script allo beu georges

Le $* contiendra "allo beu georges", c'est à dire tous les paramètres un après l'autre.  Remarquez que ces paramètres sont bien sûr séparés par des espaces puisque c'est comme ça que l'usager les a entrés...  Fort utile pour les inclure dans un "for"!

On pourrait faire par exemple:

for VAR in $*
do
   echo "$VAR!"
done

Que ferait ce script simpliste selon vous?

Retour à la table des matières de la section

4.4 - Quelques petits exercices tout simples

(voyez les solutions à ces exercices ici)

  1. Modifiez le script "gestion" que vous aviez fait à la section 2 pour utiliser un select plutôt que des echo et un read pour gérer votre menu. N'oubliez pas d'ajouter une option pour quitter le script, puisque le select tournera en boucle.
  2. Créez un script appelé "nombres" qui affiche les nombres entiers d'une borne à une autre. On appellera votre script ainsi:
    nombres 1 15

    Votre script devra dans ce cas afficher les nombres de 1 à 15. Toutefois, on pourra également l'appeler ainsi:

    nombres 15 1

    Dans ce cas, votre script devra afficher les nombres de 15 à 1, à rebours. Utilisez une boucle while pour afficher en ordre croissant et une boucle until pour afficher en ordre décroissant.

    Prenez pour acquis que l'usager appelle toujours votre script correctement, inutile de valider le nombre de paramètres (à moins que vous ayez envie de le faire, dans ce cas, laissez-vous aller).

  3. Créez un script appelé "verify" qui accepte en paramètre des chemins et noms de fichiers (ou de répertoires) complets (autant que désiré) et qui nous dira si ces fichiers (ou répertoires) existent sur le système.

    Par exemple, l'usager pourrait entrer:

    verify /usr/sbin/groupadd /usr/bin/georges /etc /dev/null

    Le script lui dirait alors:

    Le fichier /usr/sbin/groupadd existe
    /usr/bin/georges n'existe pas
    Le répertoire /etc existe
    /dev/null existe mais n'est ni un fichier ni un repertoire

    Évidemment l'usager pourra entrer autant de noms de fichiers (ou de répertoires) qu'il voudra. Vous aurez besoin d'imbriquer des if ou d'utiliser les && et || pour y arriver. Voici quelque chose qui pourra vous simplifier la tâche: la switch -e fonctionne exactement comme -f ou -d pour vérifier l'existence d'un fichier ou d'un répertoire. La seule différence est que -e retourne vrai si quelque chose existe, peu importe ce qu'il est.

  4. Assurez-vous de bien comprendre le fonctionnement de cet exemple. Écrivez dans un simple fichier texte votre explication de son fonctionnement.

Retour à la table des matières de la section