Linux sans magie #32 — Conditions Bash : comprendre test, [ ], [[ ]] et if
Comprendre les conditions Bash avec test, [ ], [[ ]], les tests sur chaînes, nombres et fichiers, puis construire des blocs if, elif et else lisibles.
Dans les deux articles précédents, nous avons vu deux briques essentielles de Bash : les codes de retour, puis les variables et le quoting.
Nous pouvons maintenant les réunir pour répondre à une question simple : comment demander à Bash d’exécuter une action seulement si une condition est vraie ?
En Bash, une condition n’est pas un type spécial séparé du reste du shell. Les constructions conditionnelles s’appuient sur le statut de sortie des commandes : 0 représente le succès, donc le cas considéré comme vrai, et un statut non nul représente le cas faux ou un échec selon la commande.
Dans ce #32, nous allons utiliser test, [ ... ], [[ ... ]] et if pour vérifier des chaînes, des nombres et des fichiers, avec un lab local non destructif.
Une condition Bash repose sur un statut de sortie
Commençons avec une commande déjà connue :
true
printf 'status=%s\n' "$?"
Résultat attendu :
status=0
Puis :
false
printf 'status=%s\n' "$?"
Résultat attendu :
status=1
Une construction if exploite exactement cette logique : elle exécute une ou plusieurs commandes de test et choisit une branche selon leur statut final.
Forme minimale :
if commande
then
action
fi
Si commande se termine avec le statut 0, action est exécutée.
test : une commande dédiée aux expressions conditionnelles
Bash fournit le builtin test.
Exemple :
nom='serveur'
test "$nom" = 'serveur'
printf 'status=%s\n' "$?"
Le test est vrai, donc le statut attendu est 0.
Essayons une valeur différente :
test "$nom" = 'routeur'
printf 'status=%s\n' "$?"
Le statut attendu est 1.
Avec test, chaque opérateur et chaque opérande doit être transmis comme un argument distinct.
Cette commande est donc correcte :
test "$nom" = 'serveur'
Une écriture compacte comme :
test "$nom"='serveur'
ne représente pas la même expression : le signe = n’est plus un argument séparé.
[ ... ] : une autre forme de test
La syntaxe avec crochets est très courante :
[ "$nom" = 'serveur' ]
Dans ce contexte, [ est une commande et ] doit être son dernier argument.
Les espaces ne sont donc pas décoratifs.
Cette forme est correcte :
[ "$nom" = 'serveur' ]
Celles-ci sont incorrectes :
["$nom" = 'serveur']
[ "$nom" = 'serveur']
Le shell doit pouvoir distinguer [, les opérandes, l’opérateur et le ] final comme des mots séparés.
Tester une chaîne vide ou non vide
Deux opérateurs courants sont :
-z chaîne: vrai si la chaîne a une longueur nulle ;-n chaîne: vrai si la chaîne a une longueur non nulle.
Exemple :
valeur=''
if [ -z "$valeur" ]
then
printf 'La valeur est vide.\n'
fi
Résultat attendu :
La valeur est vide.
Avec une valeur non vide :
valeur='production'
if [ -n "$valeur" ]
then
printf 'Valeur reçue : %s\n' "$valeur"
fi
Les guillemets autour de "$valeur" restent une bonne habitude avec [ : ils préservent la valeur comme un seul argument, notamment lorsqu’elle contient des espaces ou lorsqu’elle est vide.
Comparer deux chaînes
Pour une égalité simple avec [ :
environnement='production'
if [ "$environnement" = 'production' ]
then
printf 'Mode production.\n'
fi
Pour tester une différence :
if [ "$environnement" != 'test' ]
then
printf 'Ce n est pas l environnement de test.\n'
fi
Ici, = et != comparent des chaînes.
Nous verrons plus loin que les nombres utilisent d’autres opérateurs.
Comparer des nombres avec -eq, -lt et les opérateurs associés
Pour les entiers, les opérateurs les plus utiles sont :
-eq: égal ;-ne: différent ;-lt: inférieur ;-le: inférieur ou égal ;-gt: supérieur ;-ge: supérieur ou égal.
Exemple :
utilisation=72
if [ "$utilisation" -ge 80 ]
then
printf 'Seuil critique.\n'
else
printf 'Seuil non atteint.\n'
fi
Avec 72, le résultat attendu est :
Seuil non atteint.
Évitez de remplacer automatiquement -eq par = dans ce type de test : avec [, = est un opérateur de comparaison de chaînes, alors que -eq exprime explicitement une comparaison arithmétique d’entiers.
Préparer un lab de fichiers
Créons un environnement temporaire :
LAB_DIR="$(mktemp -d)"
printf 'Lab : %s\n' "$LAB_DIR"
cd "$LAB_DIR"
Créons un fichier et un répertoire :
printf '%s\n' 'configuration=active' > configuration.txt
mkdir sauvegardes
Vérification :
ls -ld configuration.txt sauvegardes
Nous pouvons maintenant tester plusieurs propriétés sans modifier le système.
Tester l’existence et le type d’un chemin
Parmi les expressions conditionnelles Bash courantes :
-e chemin: le chemin existe ;-f chemin: le chemin existe et désigne un fichier régulier ;-d chemin: le chemin existe et désigne un répertoire.
Exemple :
if [ -e configuration.txt ]
then
printf 'Le chemin existe.\n'
fi
Testons le type :
if [ -f configuration.txt ]
then
printf 'configuration.txt est un fichier régulier.\n'
fi
if [ -d sauvegardes ]
then
printf 'sauvegardes est un répertoire.\n'
fi
Ces tests permettent de construire des gardes simples avant une lecture, une copie ou une autre opération.
Utiliser if, elif et else
Un bloc conditionnel peut comporter plusieurs branches :
if condition1
then
action1
elif condition2
then
action2
else
action3
fi
Prenons un niveau d’utilisation fictif :
utilisation=87
if [ "$utilisation" -ge 90 ]
then
printf 'Niveau : critique\n'
elif [ "$utilisation" -ge 75 ]
then
printf 'Niveau : attention\n'
else
printf 'Niveau : normal\n'
fi
Résultat attendu :
Niveau : attention
Bash évalue les branches dans l’ordre. Dès qu’une condition réussit, son bloc est exécuté et les branches suivantes ne sont pas choisies.
[[ ... ]] : la construction conditionnelle Bash
Bash fournit aussi la construction :
[[ expression ]]
Contrairement à [, il s’agit d’une construction conditionnelle propre à Bash.
Exemple :
nom='rapport final.txt'
if [[ -n $nom ]]
then
printf 'Nom : %s\n' "$nom"
fi
À l’intérieur de [[ ... ]], Bash ne réalise pas le word splitting ni le filename expansion sur les mots de l’expression.
C’est une différence importante avec [ ... ], où le quoting des expansions reste nécessaire pour construire correctement les arguments de la commande.
Pour autant, il reste utile de conserver un quoting cohérent dans les commandes exécutées dans les branches :
printf '%s\n' "$nom"
Comparer une chaîne avec [[ ... ]]
Une égalité littérale peut s’écrire :
role='admin'
if [[ $role == 'admin' ]]
then
printf 'Rôle administrateur.\n'
fi
Avec [[ ... ]], l’opérateur == peut aussi effectuer une correspondance de motif lorsque son opérande de droite n’est pas cité.
Exemple :
fichier='rapport-final.txt'
if [[ $fichier == *.txt ]]
then
printf 'Extension txt détectée.\n'
fi
Ici, *.txt est interprété comme un motif.
Si vous voulez comparer littéralement une chaîne contenant des caractères de motif, citez l’opérande concerné :
motif='*.txt'
if [[ $motif == '*.txt' ]]
then
printf 'La valeur littérale est *.txt.\n'
fi
Cette distinction doit être volontaire : motif non cité lorsque vous voulez du pattern matching, chaîne citée lorsque vous voulez une comparaison littérale.
Combiner des conditions avec [[ ... ]]
La construction [[ ... ]] permet d’exprimer clairement des conditions combinées.
Exemple :
environnement='production'
actif='oui'
if [[ $environnement == 'production' && $actif == 'oui' ]]
then
printf 'Service de production actif.\n'
fi
Pour une alternative :
if [[ $environnement == 'production' || $environnement == 'preproduction' ]]
then
printf 'Environnement géré.\n'
fi
Pour nier une expression :
if [[ ! -f fichier-inexistant.txt ]]
then
printf 'Le fichier est absent.\n'
fi
Cette écriture est souvent plus lisible que l’accumulation de plusieurs commandes test.
Relier conditions et codes de retour
Une condition if n’exige pas forcément test, [ ou [[.
Toute commande peut servir de condition si son statut de sortie a le sens attendu.
Exemple :
if grep -q 'configuration=active' configuration.txt
then
printf 'Configuration active détectée.\n'
else
printf 'Configuration active non détectée.\n'
fi
Ici, c’est le statut retourné par grep qui pilote le if.
Comme vu dans le #30, il faut connaître la sémantique des codes de retour de la commande utilisée. Une condition n’est fiable que si son statut correspond réellement à la décision que vous voulez prendre.
Bonnes pratiques
Utilisez [ lorsque vous avez besoin d’une forme largement familière aux shells de type POSIX et que les expressions restent simples.
Utilisez [[ ... ]] lorsque votre script cible explicitement Bash et que vous voulez bénéficier de sa syntaxe conditionnelle plus sûre pour les expansions et plus lisible pour certaines combinaisons.
Avec [, citez les expansions de variables :
[ -n "$variable" ]
Utilisez les opérateurs numériques pour les entiers :
[ "$nombre" -ge 10 ]
Utilisez les tests de fichiers qui correspondent réellement au besoin : -e pour l’existence, -f pour un fichier régulier, -d pour un répertoire.
Préférez un bloc if explicite lorsque plusieurs actions dépendent d’une condition. Les chaînes && et || restent pratiques pour des cas courts, mais deviennent moins lisibles dès que la logique comporte plusieurs branches.
Sécurité
Un test conditionnel ne constitue pas à lui seul une garantie contre les changements concurrents.
Par exemple :
if [ -f "$fichier" ]
then
# utilisation du fichier
fi
vérifie un état à un instant donné. Entre le test et une opération ultérieure, le fichier peut être modifié, remplacé ou supprimé par un autre processus.
Pour les opérations sensibles, il faut donc privilégier les mécanismes atomiques ou les contrôles fournis par la commande réellement utilisée plutôt que de supposer qu’un test préalable garantit durablement l’état du système.
Les exemples de cet article restent locaux et non privilégiés.
Coûts
Le lab utilise uniquement Bash, un répertoire temporaire, un fichier texte et un sous-répertoire local. Il n’utilise aucun service cloud ni ressource facturable.
Limites et points de vigilance
Cet article ne couvre pas encore :
- les expressions arithmétiques
(( ... )); - les expressions régulières avec
[[ ... =~ ... ]]; case;- les boucles
foretwhile; - les paramètres positionnels d’un script ;
- les options comme
set -uouset -e; - les problèmes de concurrence avancés autour des fichiers.
Le périmètre reste volontairement centré sur les conditions fondamentales et les branches if.
Rollback ou nettoyage
Le lab a créé un répertoire temporaire, un fichier et un sous-répertoire.
Vérifiez d’abord le chemin :
printf '%s\n' "$LAB_DIR"
Puis quittez le lab :
cd /
Supprimez uniquement le répertoire temporaire créé au début :
rm -rf -- "$LAB_DIR"
Cette suppression est destructive pour le contenu du lab ; vérifiez donc la valeur de LAB_DIR avant de l’exécuter.
Références officielles
- Bash Reference Manual — Conditional Constructs — GNU Project
- Bash Reference Manual — Bash Conditional Expressions — GNU Project
- Bash Reference Manual — Bourne Shell Builtins — GNU Project
- Bash Reference Manual — Shell Parameter Expansion — GNU Project
Conclusion
Les conditions Bash deviennent simples lorsque l’on revient au mécanisme de base : une commande renvoie un statut, et if choisit une branche à partir de ce statut.
Retenez ensuite trois niveaux :
test expression
[ expression ]
[[ expression ]]
test et [ fournissent les tests classiques ; [[ ... ]] ajoute une construction conditionnelle propre à Bash. Avec ces outils, vous pouvez déjà écrire des décisions lisibles sur des chaînes, des nombres et des fichiers sans transformer le shell en langage mystérieux.
