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 -e ne déclenche pas une sortie dans plusieurs contextes courants, notamment les commandes testées par if ou while, 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 -sSf pour curl (silencieux, échec, afficher les erreurs) forcent curl à 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écuter commande2 uniquement si commande1 réussit.
  • commande1 || commande2 : Exécuter commande2 uniquement si commande1 é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.