8. Structures de contrôle case et while

8.1. Choix multiple case

Syntaxe :

          case mot in

          [ modèle [ | modèle ] ... ) suite_de_commandes ;; ] ...

          esac

Le shell évalue la valeur de mot puis compare séquentiellement cette valeur à chaque modèle. Dès qu'un modèle correspond à la valeur de mot, la suite de commandes associée est exécutée, terminant l'exécution de la commande interne composée case.

Les mots case et esac sont des mots-clé ce qui signifie que chacun d’eux doit être le premier mot d’une commande.

suite_de_commandes doit se terminer par deux caractères point-virgule collés, de manière à ce qu’il n’y ait pas d’ambiguïté avec l’enchaînement séquentiel de commandes cmd1 ; cmd2.

Un modèle peut être construit à l’aide des caractères et expressions génériques de bash [cf. § Caractères et expressions génériques]. Dans ce contexte, le symbole | signifie OU.

Pour indiquer le cas par défaut, on utilise le modèle *. Ce modèle doit être placé à la fin de la structure de contrôle case.

Le code de retour de la commande composée case est celui de la dernière commande exécutée de suite_de_commandes.

  • Exemple 1 : programme shell oui affichant OUI si l’utilisateur a saisi le caractère o ou O

    #!/bin/bash
    
    #@(#)oui
    
    read –p ″Entrez votre réponse : ″  rep
    case $rep in
    o|O ) echo OUI ;;
    *)    echo Indefini
    esac

    Rq : il n’est pas obligatoire de terminer par ;; la dernière suite_de_commandes

  • Exemple 2 : programme shell nombre prenant une chaîne de caractères en argument et qui affiche cette chaîne si elle est constituée d’une suite de chiffres

    #!/bin/bash
    
    #@(#)nombre
    
    shopt -s extglob
    case $1 in
    +([[:digit:]]) ) echo ″$1 est une suite de chiffres″ ;;
    esac

8.2. Itération while

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 re-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 à zéro.

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.

$ while who | grep sanchis >/dev/null
> do
>   echo ″l’utilisateur sanchis est encore connecte″
>   sleep 5
> done
l’utilisateur sanchis est encore connecte

^C
$

Le fonctionnement est le suivant : la suite de commandes who | grep sanchis est exécutée. Son code de retour sera égal à 0 si le mot sanchis est présent dans les résultats engendrés par l’exécution de la commande unix who, c’est à dire si l’utilisateur sanchis est connecté. Dans ce cas, la ou les lignes correspondant à cet utilisateur seront affichées sur la sortie standard de la commande grep. Comme seul le code de retour est intéressant et non le résultat, la sortie standard de grep est redirigée vers le puits (/dev/null). Enfin, le message est affiché et le programme « s’endort » pendant 5 secondes. Ensuite, la suite de commandes who | grep sanchis est ré exécutée. Si l’utilisateur s’est totalement déconnecté, la commande grep ne trouve aucune ligne contenant la chaîne sanchis, son code de retour sera égal à 1 et le programme sortira de l’itération.

  • 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
    >   who | cut -d' ' -f1 >>fic    => traitement à effectuer
    >   sleep 300                    => temporisation
    > done &
     [1] 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 wh qui affiche les noms des utilisateurs connectés

    #!/bin/bash
    
    #@(#)wh
    
    who > tmp
    while read nom reste
      do
        echo $nom
      done < tmp
    rm tmp

    Lorsque le fichier à lire est créé par une commande cmd, comme dans le programme wh, on peut utiliser la syntaxe :

              cmd | while read [ var1 ... ]

                 do

                    commande(s) de traitement de la ligne courante

                 done

    Exemple : wh1

    #!/bin/bash
    
    #@(#)wh1
    
    who | while read nom reste
            do
              echo $nom
            done

    Par rapport au programme shell wh, il est inutile de gérer le fichier temporaire tmp.

  • Commande interne while et performances

    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 supplémentaire (la commande unix cut), on peut s’affranchir de l’itération while dans le programme wh1. Il suffit d’extraire le premier champ de chaque ligne.

    La commande unix cut permet de sélectionner un ou plusieurs champs de chaque ligne d’un fichier texte. Un champ peut être spécifié en précisant le caractère séparateur de champ (par défaut, il s’agit du caractère tabulation) avec l’option –d ; les numéros de champs doivent alors être indiqués avec l’option -f.

    Programme wh1.cut :

    - - - - - - - - - - - - - - - - -
    #!/bin/bash
    # @(#) wh1.cut
    
    who | cut -d ' ' -f1
    - - - - - - - - - - - - - - - - -

    $ wh1.cut
    sanchis
    root
    $

    Dans le programme wh1.cut, on précise que la commande cut doit prendre comme séparateur le caractère espace (-d ' ') et que seul le premier champ de chaque ligne doit être extrait (-f1).

    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 ou simple redirection).

    Prenons l’exemple où l’on souhaite modifier 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 date à l’entrée de la commande interne while. Après lecture de la première (et unique) ligne, la valeur de la variable var est modifiée. 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.

    Programme maj :

    - - - - - - - - - - - - - - - - -
    #!/bin/bash
    # @(#) maj
    
    var=0
    date | while read
             do
               var=1
             done
    
    echo $var
    - - - - - - - - - - - - - - - - -

    $ maj
    0
    $

    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.

    Programme maj1 :

    - - - - - - - - - - - - - - - - -
    #!/bin/bash
    # @(#) maj1
    
    var=0
    date > tmp
    while read
      do
        var=1
      done < tmp
    rm tmp
    
    echo $var
    - - - - - - - - - - - - - - - - -

    $ maj1
    1
    $