Choix multiples : case et les tests avancés
Notions théoriques
Les limites d'une cascade de if
Vous savez déjà tester une variable avec if/elif/else. Mais dès qu'il faut comparer une seule variable à de nombreuses valeurs possibles (une sous-commande, une réponse utilisateur, une extension de fichier...), une longue cascade de elif devient vite difficile à lire. Bash propose une structure dédiée à ce cas précis : case.
La structure case
case "$variable" in
motif1)
# instructions
;;
motif2)
# instructions
;;
*)
# motif par défaut
;;
esac
case "$variable" inouvre le bloc — pensez à quoter la variable, comme pour un[ ].- Chaque motif est suivi d'une parenthèse fermante
). ;;marque la fin d'une branche : contrairement auswitchd'autres langages, il n'y a pas de « fall-through » vers la branche suivante par défaut.*)est le motif fourre-tout, équivalent duelse— capte toute valeur non prévue par les motifs précédents.esac(« case » à l'envers) ferme le bloc, commefiferme unifetdoneferme une boucle.
Vous croiserez parfois ;& ou ;;& (Bash 4+), qui permettent de poursuivre l'exécution dans la branche suivante au lieu de sortir. C'est rare en pratique — la forme ;; reste la norme.
Les motifs d'un case
Un motif case accepte les caractères spéciaux du globbing, les mêmes que pour les noms de fichiers :
| Motif | Signification |
|---|---|
* | n'importe quelle chaîne (y compris vide) |
? | un seul caractère quelconque |
[0-9] | un seul chiffre |
[Oo]ui | Oui ou oui |
start|demarrer | start ou demarrer (alternative avec |) |
case "$1" in
start|demarrer)
echo "Démarrage du service"
;;
stop|arreter)
echo "Arrêt du service"
;;
status)
echo "Statut du service"
;;
*)
echo "Commande inconnue : $1"
;;
esac
Un case est aussi très utile pour traiter une réponse read, où l'utilisateur peut taper plusieurs variantes valables :
read -p "Continuer ? (o/n) " reponse
case "$reponse" in
o|O|oui|Oui)
echo "On continue"
;;
*)
echo "Arrêt demandé"
exit 0
;;
esac
Le test [[ ]] : une alternative plus riche à [ ]
Nous avions annoncé en séance 4 du niveau 1 l'existence de [[ ]], une syntaxe de test spécifique à Bash (contrairement à [ ], disponible dans tous les shells POSIX). Elle apporte plusieurs avantages :
if [[ "$fichier" == *.tar.gz ]]; then
echo "C'est une archive"
fi
- Pas de découpage de mots ni de globbing sur une variable non quotée à l'intérieur —
[[ $fichier == *.tar.gz ]]fonctionne même sans guillemets, même si les garder reste une bonne habitude. &&et||s'utilisent directement à l'intérieur des crochets doubles :if [[ -d "$d" && -r "$d" ]].- Comparaison par motif avec
==(comme dans l'exemple ci-dessus) — impossible avec[ ]. - Comparaison par expression régulière avec
=~:if [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]](nous resterons ici à un usage simple).
[ ] reste utile à connaître : c'est la seule forme portable, disponible même dans des shells plus anciens que Bash (sh, dash). Pour un script destiné à rester en Bash, [[ ]] est aujourd'hui préférable au quotidien.
Exemple pratique
#!/bin/bash
# Script : service_local.sh
# Rôle : simuler la gestion d'un service (start/stop/status)
if [ $# -eq 0 ]; then
echo "Usage : $0 {start|stop|status|restart}" >&2
exit 1
fi
case "$1" in
start)
echo "Démarrage du service..."
;;
stop)
echo "Arrêt du service..."
;;
restart)
echo "Redémarrage du service..."
;;
status)
echo "Le service est actif."
;;
*)
echo "Commande inconnue : $1" >&2
echo "Usage : $0 {start|stop|status|restart}" >&2
exit 1
;;
esac
Remarquez la ressemblance avec les vieux scripts /etc/init.d/ : c'est exactement ce patron case sur $1 qu'ils utilisent pour interpréter start, stop, status...
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Nous reprenons sauvegarde.sh là où le niveau 1 l'avait laissé (version structurée en fonctions), en remplaçant sa confirmation par un case et en lui ajoutant un mode de fonctionnement.
Étape 1 — Remplacer la confirmation par un case
Dès qu'une variable peut légitimement contenir plusieurs valeurs valides (o, O, oui...), un case reste plus lisible qu'un if chargé de multiples ||.
Étape 2 — Ajouter un mode de fonctionnement
Un case sans motif *) laisse passer silencieusement une valeur imprévue. Prévoyez toujours cette branche, ne serait-ce que pour afficher un message d'erreur explicite.
Étape 3 — Filtrer par extension avec [[ ]]
[[ "$fichier" == *.log ]] est plus lisible que l'équivalent avec case pour un test isolé au milieu d'une boucle — réservez case aux choix à plusieurs branches, et [[ ]] aux tests ponctuels par motif.