Stratégies efficaces de gestion des erreurs dans les scripts Bash
Utilisez le mode strict, les pièges, les codes de sortie et des messages d'erreur clairs sur stderr pour que les scripts Bash échouent en toute sécurité et nettoient après eux.
Stratégies efficaces de gestion des erreurs dans les scripts Bash
La gestion des erreurs dans les scripts Bash est importante car un script qui échoue silencieusement peut copier des fichiers partiels, déployer du code cassé ou supprimer le mauvais chemin. Vous voulez que votre script s'arrête lorsqu'une étape critique échoue, explique ce qui s'est passé et nettoie les fichiers temporaires avant de se terminer.
Les modèles ci-dessous couvrent les éléments dont vous avez le plus souvent besoin : mode strict, vérifications explicites, trap et rapport d'erreur simple.
Les bases : Comprendre le statut de sortie
Dans le monde Unix, chaque commande exécutée renvoie un statut de sortie (ou code de sortie), une valeur entière indiquant le résultat de son opération. Ce statut est immédiatement stocké dans la variable spéciale $?.
- Code de sortie 0 : Par convention, cela signifie succès (ou 'vrai').
- Codes de sortie 1–255 : Cela signifie échec (ou 'faux'). Des codes spécifiques correspondent souvent à des types d'échec spécifiques (par exemple, 1 pour les erreurs générales, 127 pour commande introuvable).
Les scripts fiables doivent vérifier le statut de sortie des commandes critiques et renvoyer un code non nul significatif si le script échoue.
Stratégie de base 1 : Le trio défensif de script
Pour tout script d'automatisation sérieux, vous devez commencer par appliquer trois options fondamentales immédiatement après la ligne shebang (#!/bin/bash). Ces options imposent un comportement strict et prévisible.
1. Sortie immédiate en cas d'échec (set -e)
L'option set -e (ou set -o errexit) stipule que le script doit se terminer immédiatement si une commande échoue (renvoie un statut de sortie non nul).
C'est ce qu'on appelle souvent le principe "échouer rapidement" et empêche le script de poursuivre des actions potentiellement destructrices en utilisant des résultats prérequis incomplets ou échoués.
#!/bin/bash
set -e
echo "Démarrage du processus..."
mkdir /tmp/test_dir
cp non_existent_file /tmp/test_dir/ # Cette commande échoue (code de sortie > 0)
echo "Cette ligne ne sera pas exécutée." # Le script se termine ici
Avertissement : Pièges de
set -e
set -ene déclenche pas une sortie dans plusieurs contextes courants, notamment les commandes testées parifouwhile, les commandes dans la plupart des listes&&ou||, et les commandes dont le statut est inversé avec!. Traitez-le comme un filet de sécurité, pas comme un remplacement des vérifications claires autour des échecs attendus.
2. Traiter les variables non définies comme des erreurs (set -u)
L'option set -u (ou set -o nounset) garantit que le script traite l'utilisation de toute variable non définie comme une erreur, provoquant la sortie immédiate du script (similaire à set -e). Cela évite les bogues subtils où une faute de frappe dans un nom de variable conduit à une chaîne vide passée à une commande critique.
#!/bin/bash
set -u
# echo "La variable est : $UNDEFINED_VAR" # Le script échoue et se termine ici
MY_VAR="défini"
echo "La variable est : ${MY_VAR}"
3. Gestion des pipelines de commandes (set -o pipefail)
Par défaut, un pipeline de commandes (commande1 | commande2 | commande3) ne signale que le statut de sortie de la dernière commande (commande3). Si commande1 échoue mais que commande3 réussit, $? sera 0, masquant l'échec.
set -o pipefail modifie ce comportement, garantissant que le pipeline renvoie un statut non nul si une commande du pipeline échoue. Ceci est crucial pour un traitement fiable des données.
#!/bin/bash
set -o pipefail
# La commande `false` se termine toujours par 1
# Sans pipefail, cette ligne renverrait 0 car `cat` réussit.
false | cat # Renvoie 1 à cause de pipefail
if [ $? -ne 0 ]; then
echo "Le pipeline a échoué."
fi
Bonne pratique : L'en-tête
Commencez toujours les scripts robustes avec les options défensives combinées :
#!/bin/bash set -euo pipefail
Stratégie de base 2 : Vérifications manuelles et exécution conditionnelle
Bien que set -e gère la plupart des échecs, vous devez souvent vérifier manuellement le statut de la commande, en particulier lorsque l'échec est attendu ou nécessite une journalisation spécifique.
La vérification avec l'instruction if
La méthode standard pour vérifier le succès d'une commande consiste à capturer son statut de sortie dans un bloc if. Cette méthode remplace le comportement de set -e, vous permettant de gérer explicitement l'erreur.
#!/bin/bash
set -euo pipefail
TEMP_FILE="/tmp/data_processing_$$/config.dat"
# Tentative de création du répertoire ; gérer l'échec explicitement
if ! mkdir -p "$(dirname "$TEMP_FILE")"; then
echo "[ERREUR] Impossible de créer le répertoire temporaire." >&2
exit 1
fi
# Tentative de récupération des données
if ! curl -sSf https://api.example.com/data > "$TEMP_FILE"; then
echo "[ERREUR] Échec de la récupération des données depuis l'API." >&2
exit 2
fi
echo "Données récupérées avec succès."
Astuce : Les drapeaux
-sSfpourcurl(silencieux, échec, afficher les erreurs) forcentcurlà renvoyer un code de sortie non nul en cas d'erreur HTTP, facilitant la gestion des erreurs.
Utilisation des opérateurs de court-circuit (&& et ||)
Ces opérateurs logiques fournissent des moyens concis d'enchaîner des commandes en fonction du succès (&&) ou de l'échec (||).
commande1 && commande2: Exécutercommande2uniquement sicommande1réussit.commande1 || commande2: Exécutercommande2uniquement sicommande1échoue.
# Exemple : Créer un répertoire ET copier un fichier, échouer si l'une des étapes échoue
mkdir logs && cp /var/log/syslog logs/system.log
# Exemple : Tenter une sauvegarde, OU journaliser l'erreur et quitter si la sauvegarde échoue
pg_dump database > backup.sql || { echo "La sauvegarde a échoué !" >&2; exit 10; }
Stratégie avancée 3 : Nettoyage garanti avec trap
Lorsqu'un script manipule des fichiers temporaires, des fichiers de verrouillage ou des connexions réseau établies, des sorties abruptes (que ce soit réussies ou dues à une erreur) peuvent laisser le système dans un état incohérent. La commande trap vous permet de définir une commande ou une fonction à exécuter lorsque le script reçoit un signal spécifique.
Le signal EXIT
Le signal EXIT est le plus utile pour le nettoyage général. La commande piégée s'exécute chaque fois que le script se termine, que la sortie soit réussie, un appel exit manuel ou une sortie déclenchée par set -e.
#!/bin/bash
TEMP_DIR=$(mktemp -d)
# Définition de la fonction de nettoyage
cleanup() {
EXIT_CODE=$?
echo "Nettoyage du répertoire temporaire : ${TEMP_DIR}"
rm -rf "$TEMP_DIR"
# Si le script s'est terminé en raison d'un échec, restaurer le code d'échec
if [ $EXIT_CODE -ne 0 ]; then
exit $EXIT_CODE
fi
}
# Définir le piège : Exécuter la fonction 'cleanup' à la sortie du script
trap cleanup EXIT
# --- Logique principale du script ---
echo "Traitement des données dans ${TEMP_DIR}"
# Simuler une opération réussie...
# ... le script continue ...
# Simuler un échec critique qui déclenche set -e
false
# Cette ligne est inaccessible, mais le nettoyage est toujours garanti.
echo "Terminé."
Gestion des signaux spécifiques (TERM, INT)
Vous pouvez également piéger des signaux de terminaison spécifiques comme TERM (demande de terminaison) ou INT (interruption, souvent Ctrl+C) pour garantir un arrêt gracieux lorsqu'un utilisateur ou un planificateur annule le travail.
trap 'echo "Script interrompu par l\'utilisateur (Ctrl+C). Abandon du nettoyage." >&2; exit 130' INT
Stratégie 4 : Rapport d'erreur et journalisation personnalisés
Un script professionnel doit utiliser une fonction d'erreur dédiée pour centraliser le rapport, garantissant la cohérence et des canaux de sortie appropriés.
Redirection des erreurs vers la sortie d'erreur standard (>&2)
Les messages d'erreur doivent toujours être imprimés sur la sortie d'erreur standard (stderr ou descripteur de fichier 2), permettant à la sortie standard (stdout ou descripteur de fichier 1) de rester propre pour les données ou les résultats réussis.
Le modèle de fonction die
Créez une fonction, souvent nommée die ou error_exit, qui gère la journalisation du message, le nettoyage (si les pièges ne sont pas utilisés) et la sortie avec un code spécifié.
# Fonction pour imprimer un message d'erreur et quitter
die() {
local msg=$1
local code=${2:-1}
echo "$(date +'%Y-%m-%d %H:%M:%S') [FATAL] : ${msg}" >&2
exit "$code"
}
# Exemple d'utilisation :
REQUIRED_VAR="$1"
if [ -z "$REQUIRED_VAR" ]; then
die "Argument requis manquant (Nom de la base de données)." 3
fi
# ... plus tard dans le script ...
if ! validate_checksum "$FILE"; then
die "Échec de la vérification de la somme de contrôle pour $FILE." 5
fi
Rendre les échecs ennuyeux
Pour une gestion fiable des erreurs dans les scripts Bash, commencez chaque script non trivial par set -euo pipefail, utilisez if ! commande; then ...; fi là où vous vous attendez à ce qu'une commande échoue, et envoyez les erreurs vers stderr. Si votre script crée des fichiers temporaires, des fichiers de verrouillage ou des sorties partielles, ajoutez trap cleanup EXIT avant que le travail risqué ne commence.
Cette combinaison maintient les petites tâches d'automatisation prévisibles et facilite le diagnostic des échecs de production.