Comprendre les codes de sortie : Gestion efficace des erreurs avec $? et exit

Utilisez les codes de sortie Bash, $?, exit, set -e et pipefail pour rendre les échecs de script clairs et contrôlés.

Comprendre les codes de sortie : Gestion efficace des erreurs avec $? et exit

Lorsqu'un script Bash échoue, le code de sortie indique à l'appelant ce qui doit se passer ensuite : continuer, réessayer, alerter ou arrêter. Comprendre les codes de sortie, $? et exit fait la différence entre une automatisation qui cache les échecs et une automatisation qui les signale clairement.

Ce guide montre comment Bash suit l'état des commandes et comment vous pouvez utiliser cet état pour une gestion des erreurs simple et fiable.

Le concept des statuts de sortie

Chaque commande ou programme exécuté dans un environnement shell de type Unix—qu'il s'agisse d'une commande intégrée comme cd, d'un utilitaire externe comme grep, ou d'un autre script shell—renvoie une valeur entière à la fin de son exécution. Cet entier est le code de sortie, qui signale le résultat de l'opération au processus appelant.

La convention standard

La convention pour les codes de sortie est universellement reconnue :

  • 0 (Zéro) : Signifie succès. La commande s'est exécutée exactement comme prévu, sans erreur.
  • 1 à 255 : Signifie échec ou conditions d'erreur spécifiques. Ces valeurs non nulles indiquent que quelque chose s'est mal passé. Des nombres plus élevés correspondent souvent à des types d'erreurs spécifiques (par exemple, fichier non trouvé, permission refusée, erreur de syntaxe), bien que la signification exacte dépende du programme spécifique.

Remarque sur la plage : Bien que les codes de sortie soient techniquement une valeur de 8 bits (0-255), les scripts shell ne se préoccupent généralement que de 0 pour le succès et de non nul pour l'échec. Les codes de sortie supérieurs à 255 sont généralement tronqués ou interprétés modulo 256 par le shell.

Inspecter le dernier code de sortie : La variable $?

La variable shell spéciale $? (dollar point d'interrogation) est centrale pour surveiller l'état des commandes. Immédiatement après l'exécution de toute commande, le shell stocke son code de sortie dans $?.

Comment utiliser $?

Vous devez vérifier $? immédiatement après la commande qui vous intéresse, car toute commande ultérieure (même l'affichage de la variable) écrasera sa valeur.

Exemple 1 : Vérifier le succès et l'échec

# 1. Une commande réussie
echo "Test réussi" > /dev/null
echo "Code de sortie pour succès : $?"

# 2. Une commande en échec (par exemple, essayer de lister un fichier inexistant)
ls /chemin/non/existant
echo "Code de sortie pour échec : $?"

Sortie attendue :

Code de sortie pour succès : 0
ls: impossible d'accéder à '/chemin/non/existant': Aucun fichier ou dossier de ce type
Code de sortie pour échec : 2

Implémenter une vérification conditionnelle des erreurs

Connaître simplement le code de sortie ne suffit pas ; la puissance vient de l'utilisation de cette information pour contrôler le flux du script. Cela se fait généralement avec des instructions if ou des opérateurs de court-circuit (&& et ||).

Utiliser des instructions if

C'est la manière la plus explicite de gérer les erreurs :

if grep -q "données importantes" fichier_log.txt;
alors
    echo "Données trouvées avec succès."
sinon
    DERNIER_STATUT=$?
    echo "Erreur : Grep a échoué avec le statut $DERNIER_STATUT. Données non trouvées."
    # Envisager de quitter ici si le script ne peut pas continuer
fi

Dans l'exemple ci-dessus, grep -q supprime la sortie (-q) et renvoie 0 seulement si une correspondance est trouvée. La structure if vérifie automatiquement le statut de sortie, mais capturer explicitement $? dans le bloc sinon est utile pour une journalisation détaillée.

Utiliser la logique de court-circuit (&& et ||)

Pour des vérifications séquentielles simples, les opérateurs de court-circuit fournissent une gestion concise des erreurs :

  • && (ET) : La commande suivant && ne s'exécute que si la commande précédente a réussi (renvoyé 0).
  • || (OU) : La commande suivant || ne s'exécute que si la commande précédente a échoué (renvoyé non nul).

Exemple 2 : Gestion concise des erreurs

# 1. Exécuter 'traiter_donnees' UNIQUEMENT SI 'recuperer_donnees' réussit
recuperer_donnees.sh && ./traiter_donnees.sh

# 2. Exécuter 'envoyer_alerte' UNIQUEMENT SI l'opération principale échoue
rsync -a source/ dest/ || echo "RSync a échoué le $(date)" >> /var/log/erreurs_rsync.log

Contrôler la terminaison du script avec exit

La commande exit est utilisée pour terminer immédiatement le script shell ou la fonction en cours et renvoyer un statut de sortie spécifié à l'appelant (qui peut être un autre script ou le terminal de l'utilisateur).

Syntaxe et utilisation

La syntaxe est simplement exit [code_statut].

Si aucun statut n'est fourni, exit prend par défaut le statut de la commande de premier plan la plus récemment exécutée. Si vous appelez explicitement exit 0 sans exécuter de commande au préalable, il renvoie 0.

Exemple 3 : Quitter en cas d'échec de pré-condition

Ce script s'assure qu'un fichier de configuration requis existe avant de continuer.

FICHIER_CONF="/etc/app/config.conf"

si [[ ! -f "$FICHIER_CONF" ]]; alors
    echo "Erreur : Fichier de configuration introuvable à $FICHIER_CONF."
    # Terminer le script immédiatement avec un code d'erreur spécifique (par exemple, 20)
    exit 20 
fi

echo "Configuration chargée. Continuation du script..."
# ... reste du script
exit 0

Bonne pratique : Utiliser des codes de sortie significatifs

Bien que 0 et 1 couvrent la plupart des cas de base, utiliser différents codes non nuls aide le script appelant à diagnostiquer le problème exact :

Code Signification (Exemple)
0 Succès
1 Erreur générale fourre-tout
2-10 Erreurs de syntaxe, problèmes d'analyse des arguments
20 Prérequis manquant (par exemple, fichier non trouvé)
30 Problème de permission

Faire échouer les scripts rapidement : La commande set

Pour une fiabilité maximale dans les scripts complexes, il est fortement recommandé d'activer la vérification globale des erreurs en utilisant les options de la commande set en haut de votre script :

#!/bin/bash

# Quitter immédiatement si une commande se termine avec un statut non nul.
set -e

# Traiter les variables non définies comme une erreur lors de la substitution.
set -u

# Pipefail : Garantit que le statut de retour d'un pipeline est le statut de la commande la plus à droite qui s'est terminée avec un statut non nul.
set -o pipefail

# (Optionnel mais utile) Afficher les commandes au fur et à mesure de leur exécution pour le débogage
# set -x 

# Si une commande ci-dessous échoue, le script s'arrête immédiatement.
ls /chemin/valide && grep motif fichier.txt && ./prochaine_etape.sh

# La ligne suivante ne s'exécutera QUE si toutes les commandes précédentes ont réussi.
echo "Toutes les étapes terminées."

Lorsque set -e est actif, de nombreux statuts non nuls non gérés arrêtent le script avant que les commandes ultérieures ne s'exécutent sur de mauvaises hypothèses. Il a des exceptions dans les conditionnelles, les pipelines et les commandes composées, donc gérez toujours les échecs attendus explicitement.

Par exemple, grep renvoie 1 lorsqu'il ne trouve aucune correspondance. Cela peut être un résultat normal, pas une erreur fatale :

si grep -q "PRET" statut.txt; alors
    echo "Le service est prêt."
sinon
    echo "Le service n'est pas encore prêt."
fi

À retenir

Vérifiez les commandes critiques là où elles s'exécutent, écrivez les erreurs sur stderr et quittez avec un statut non nul lorsque le script ne peut pas continuer en toute sécurité. Utilisez set -euo pipefail pour les scripts à échec rapide, mais ne comptez pas dessus comme votre seule stratégie de gestion des erreurs.