11.4. Tests et branchements

Les constructions case et select ne sont pas techniquement des boucles puisqu' elles n'exécutent pas un bloc de code de façon itérative. Néanmoins, comme les boucles, elles orientent le flot d'exécution du programme suivant certaines conditions au début ou à la fin du bloc.

Contrôler le flot du programme dans un bloc de code

case (in) / esac

La syntaxe avec case est l'équivalent en shell de switch en C/C++. Elle permet le branchement vers l'un parmi plusieurs blocs de code, d'après des tests conditionnels. Elle agit comme une espèce de raccourcis pour de multiples instructions if/then/else et c'est un outil approprié pour la création de menus.

case "$variable" in

"$condition1" )
commande...
;;

"$condition2" )
commande...
;;


esac

[Note]

Note

  • Protéger les variables n'est pas obligatoire car la séparation de mots n'est pas effective.

  • Chaque ligne de test se termine par une parenthèse fermante ). DIFF63 [53]

  • Chaque bloc conditionnel de termine par un double point virgule ;;.

  • Si une condition est vraie, alors la commande associée s'exécute et le bloc case prend fin.

  • Le bloc case tout entier se termine par un esac (case épelé à l'envers).

Exemple 11.24. Utiliser case

#!/bin/bash
# Tester des suites de caractères.

echo; echo "Appuyez sur une touche, puis faites ENTER."
read Touche

case "$Touche" in
  [[:lower:]]   ) echo "Lettre minuscule";;
  [[:upper:]]   ) echo "Lettre majuscule";;
  [0-9]   ) echo "Nombre";;
  *       ) echo "Ponctuation, espace blanc ou autre";;
esac  #  Permet un ensemble de caractères dans des [crochets].
      #+ ou des ensembles POSIX dans des [[crochets doubles]].

#  Dans la première version de cet exemple,
#+ les tests des caractères minuscules/majuscules étaient
#+ [a-z] et [A-Z].
#  Ceci ne fonctionne plus avec certaines locales et/ou distributions Linux.
#  POSIX est plus portable.
#  Merci à Frank Wang de me l'avoir fait remarquer.

# Exercice :
# ---------
# Ce script accepte un simple appui sur une touche, puis se termine.
# Modifiez le script pour qu'il accepte une saisie répétée,
# rapportez chaque appui sur une touche, et terminez lors de l'appui sur "X".
# Astuce : mettre tout dans une boucle "while".

exit 0

Exemple 11.25. Créer des menus en utilisant case

#!/bin/bash

# Base de données d'adresse.

clear # Efface l'écran.

echo "          Liste de Contacts"
echo "          -----------------"
echo "Choisissez une des personnes suivantes:" 
echo
echo "[E]vans, Roland"
echo "[J]ones, Mildred"
echo "[S]mith, Julie"
echo "[Z]ane, Morris"
echo

read personne

case "$person" in
# Notez que la variable est entre guillemets.

  "E" | "e" )
  # Accepte les entrées en majuscule ou minuscule.
  echo
  echo "Roland Evans"
  echo "4321 Floppy Dr."
  echo "Hardscrabble, CO 80753"
  echo "(303) 734-9874"
  echo "(303) 734-9892 fax"
  echo "revans@zzy.net"
  echo "Business partner & old friend"
  ;;
  # Notez le double point-virgule pour terminer chaque option.

  "J" | "j" )
  echo
  echo "Mildred Jones"
  echo "249 E. 7th St., Apt. 19"
  echo "New York, NY 10009"
  echo "(212) 533-2814"
  echo "(212) 533-9972 fax"
  echo "milliej@loisaida.com"
  echo "Ex-girlfriend"
  echo "Birthday: Feb. 11"
  ;;

# Ajoutez de l'info pour Smith & Zane plus tard.

          * )
   # Option par défaut.   
   # Entrée vide (en appuyant uniquement sur la touche RETURN) vient ici aussi.
   echo
   echo "Pas encore dans la base de données."
  ;;

esac

echo

#  Exercice:
#  --------
#  Modifier le script pour qu'il accepte plusieurs saisies,
#+ au lieu de s'arrêter après avoir affiché une seule adresse.

exit 0

Une utilisation exceptionnellement intelligente de case concerne le test des paramètres de ligne de commande.

#! /bin/bash

case "$1" in
  "") echo "Usage: ${0##*/} <nomfichier>"; exit $E_PARAM;;
                      #  Pas de paramètres en lignes de commande
                      #+ ou premier paramètre vide.
# Notez que ${0##*/} est la substitution de paramètres ${var##modèle}.
                      # Le résultat net est $0.

  -*) NOMFICHIER=./$1;; #  Si le nom de fichier passé en premier argument ($1)
                      #+ commence avec un tiret,
                      #+ le remplacez par ./$1
                      #+ pour que les commandes suivants ne l'interprètent pas
                      #+ comme une option.

  * ) NOMFICHIER=$1;;   # Sinon, $1.
esac

Voici un exemple plus direct de gestion de paramètres en ligne de commande :

#! /bin/bash


while [ $# -gt 0 ]; do    # Jusqu'à la fin des paramètres...
  case "$1" in
    -d|--debug)
              # paramètre "-d" ou "--debug" ?
              DEBUG=1
              ;;
    -c|--conf)
              CONFFILE="$2"
              shift
              if [ ! -f $CONFFILE ]; then
                echo "Erreur : le fichier indiqué n'existe pas !"
                exit $E_FICHIERCONF     # Erreur pour un fichier inexistant.
              fi
              ;;
  esac
  shift       # Vérifiez le prochain ensemble de paramètres.
done

#  À partir du script "Log2Rot" de Stefano Falsetto,
#+ faisant partie de son paquetage "rottlog".
#  Utilisé avec sa permission.

Exemple 11.26. Utiliser la substitution de commandes pour générer la variable case

#!/bin/bash
#  case-cmd.sh
#+ Utilisation de la substitution de commandes pour générer une variable "case".

case $( arch ) in   # "arch" renvoie l'architecture de la machine.
                    # Équivalent à 'uname -m'...
  i386 ) echo "Machine 80386";;
  i486 ) echo "Machine 80486";;
  i586 ) echo "Machine Pentium";;
  i686 ) echo "Machine Pentium2+";;
  *    ) echo "Autre type de machine";;
esac

exit 0

Une construction avec case peut filtrer les chaînes sur des paramètres de remplacement.

Exemple 11.27. Simple correspondance de chaîne

#!/bin/bash
# match-string.sh: simple correspondance de chaînes de caractères

chaines_correspondent ()
{
  CORRESPOND=0
  CORRESPOND_PAS=90
  PARAMS=2     # La fonction requiert deux arguments.
  MAUVAIS_PARAMS=91

  [ $# -eq $PARAMS ] || return $MAUVAIS_PARAMS

  case "$1" in
  "$2") return $CORRESPOND;;
  *   ) return $CORRESPOND_PAS;;
  esac

}  


a=un
b=deux
c=trois
d=deux


chaines_correspondent $a     # mauvais nombre de paramètres
echo $?             # 91

chaines_correspondent $a $b  # pas de correspondance
echo $?             # 90

chaines_correspondent $b $d  # correspondance
echo $?             # 0


exit 0              

Exemple 11.28. Vérification d'une entrée alphabétique

#!/bin/bash
#  isalpha.sh: Utiliser une structure "case" pour filtrer une chaîne de
#+ caractères.

SUCCES=0
ECHEC=-1

est_alpha ()  # Teste si le *premier caractère* de la chaîne est alphabétique.
{
if [ -z "$1" ]                # Pas d'argument passé?
then
  return $ECHEC
fi

case "$1" in
  [a-zA-Z]*) return $SUCCES;;  # Commence avec une lettre?
  *        ) return $ECHEC;;
esac
}             # Comparer ceci avec la fonction "isalpha ()" en C.


est_alpha2 ()   # Teste si la *chaîne entière* est alphabétique.
{
  [ $# -eq 1 ] || return $ECHEC

  case $1 in
  *[!a-zA-Z]*|"") return $ECHEC;;
               *) return $SUCCES;;
  esac
}

est_numerique ()    # Teste si la *chaîne entière* est numérique.
{                   # En d'autres mots, teste si la variable est de type entier.
  [ $# -eq 1 ] || return $ECHEC

  case $1 in
  *[!0-9]*|"") return $ECHEC;;
            *) return $SUCCES;;
  esac
}



verif_var ()  # Interface à est_alpha ().
{
if est_alpha "$@"
then
  echo "\"$*\" commence avec un caractère alpha."
  if est_alpha2 "$@"
  then        # Aucune raison de tester si le premier caractère est non alpha.
    echo "\"$*\" contient seulement des caractères alpha."
  else
    echo "\"$*\" contient au moins un caractère non alpha."
  fi  
else
  echo "\"$*\" commence avec un caractère non alpha."
              # Aussi "non alpha" si aucun argument n'est passé.
fi

echo

}

verif_numerique ()  # Interface à est_numerique ().
{
if est_numerique "$@"
then
  echo "\"$*\" contient seulement des chiffres [0 - 9]."
else
  echo "\"$*\" a au moins un caractère qui n'est pas un chiffre."
fi

echo

}

a=23skidoo
b=H3llo
c=-What?
d=What?
e=`echo $b`   # Substitution de commandes.
f=AbcDef
g=27234
h=27a34
i=27.34

verif_var $a
verif_var $b
verif_var $c
verif_var $d
verif_var $e
verif_var $f
verif_var     # Pas d'argument passé, donc qu'arrive-t'il?
#
verif_numerique $g
verif_numerique $h
verif_numerique $i


exit 0        # Script amélioré par S.C.

# Exercice:
# --------
#  Ecrire une fonction 'est_flottant ()' qui teste les nombres en virgules
#+ flottantes.
#  Astuce: La fonction duplique 'est_numerique ()',
#+ mais ajoute un test pour le point décimal nécessaire.

select

La construction select, adoptée du Korn Shell, est encore un autre outil pour construire les menus.

select variable [in liste]
do
commande...
break
done

Ceci demande à l'utilisateur d'entrer un des choix présentés dans la variable liste. Notez que select utilise l'invite $PS3 (#? ) par défaut mais que ceci peut être changé.

Exemple 11.29. Créer des menus en utilisant select

#!/bin/bash

PS3='Choisissez votre légume favori : ' # Affiche l'invite.

echo

select legume in "haricot" "carotte" "patate" "ognion" "rutabaga"
do
  echo
  echo "Votre légume favori est $legume."
  echo
  break  #  Qu'arriverait-il s'il n'y avait pas de 'break' ici ?
         #+ fin.
done

exit 0

Si on omet une liste in, alors select utilise la liste des arguments en ligne de commandes ($@) passée au script ou à la fonction contenant l'expression select.

Comparez ceci avec le comportement de la construction

for variable [in liste]

avec in liste omis.

Exemple 11.30. Créer des menus en utilisant select dans une fonction

#!/bin/bash

PS3='Choisissez votre légume favori: '

echo

choix_entre()
{
select legume
# [in list] omise, donc 'select' utilise les arguments passés à la fonction.
do
  echo
  echo "Votre légume favori est $vegetable."
  echo
  break
done
}

choix_entre haricot riz carotte radis tomate épinard
#           $1      $2  $3      $4    $5     $6
#         passé à la fonction choix_entre()

exit 0

Voir aussi l'Exemple 37.3, « Simple application de base de données, utilisant les références de variables indirectes ».



[53] Pattern-match lines may also start with a ( left paren to give the layout a more structured appearance.

case $( arch ) in   # $( arch ) returns machine architecture.
  ( i386 ) echo "80386-based machine";;
# ^      ^
  ( i486 ) echo "80486-based machine";;
  ( i586 ) echo "Pentium-based machine";;
  ( i686 ) echo "Pentium2+-based machine";;
  (    * ) echo "Other type of machine";;
esac