Scripts Bash Avancés : Bonnes Pratiques pour la Gestion des Erreurs
Améliorez la gestion des erreurs Bash avec le mode strict, les vérifications explicites, les pièges de nettoyage, les codes de sortie clairs et la journalisation stderr.
Scripts Bash Avancés : Bonnes Pratiques pour la Gestion des Erreurs
La gestion des erreurs dans les scripts Bash est ce qui empêche une petite erreur d'automatisation de devenir un problème de production désordonné. Si une sauvegarde échoue, un appel API renvoie une erreur ou un fichier temporaire est laissé derrière, votre script doit s'arrêter clairement et laisser le système dans un état connu.
Utilisez ces modèles lorsque votre script modifie des fichiers, déploie du code, communique avec des services distants ou s'exécute sans que quelqu'un surveille le terminal.
Les Fondamentaux : Comprendre les Codes de Sortie
Chaque commande exécutée dans Bash, qu'elle soit réussie ou échouée, renvoie un statut de sortie (ou code de sortie). C'est le mécanisme fondamental pour signaler les résultats des commandes.
- Code de sortie 0 : Indique une exécution réussie. Par convention, zéro signifie succès.
- Code de sortie 1-255 (non nul) : Indique une erreur, un échec ou un avertissement. Des codes non nuls spécifiques indiquent souvent des types d'erreurs spécifiques (par exemple, 1 signifie généralement une erreur générique, 2 signifie souvent une mauvaise utilisation de la commande shell).
Le statut de sortie le plus récent est stocké dans la variable spéciale $?.
# Commande réussie
ls /tmp
echo "Statut : $?"
# Statut : 0
# Commande échouée (fichier inexistant)
cat /fichier_inexistant
echo "Statut : $?"
# Statut : 1 (ou plus, selon l'erreur)
Bonne Pratique Obligatoire : Implémenter le Mode Strict
Pour tout script Bash sérieux, trois directives doivent être placées immédiatement après la ligne shebang. Collectivement, elles sont souvent appelées "mode strict". Elles poussent le script à échouer tôt plutôt que de continuer après une condition préalable brisée.
1. Quitter Immédiatement en Cas d'Erreur (set -e)
La commande set -e ou set -o errexit ordonne à Bash de quitter immédiatement le script si une commande se termine avec un statut non nul. Cela empêche les défaillances en cascade.
Attention :
set -eest ignoré dans les tests conditionnels (if,while) ou si une commande fait partie d'une liste&&ou||. Le statut d'échec doit être explicitement utilisé par la structure environnante.
2. Traiter les Variables Non Définies comme des Erreurs (set -u)
La commande set -u ou set -o nounset fait que le script se termine immédiatement s'il tente d'utiliser une variable qui n'a pas été définie (par exemple, en tapant mal $NOMFICHIER au lieu de $NOMFICHIER). Cela empêche les bogues difficiles à déboguer résultant de variables vides ou non intentionnelles.
3. Gérer les Erreurs dans les Pipelines (set -o pipefail)
Par défaut, si une série de commandes est enchaînée par des pipes (par exemple, cmd1 | cmd2 | cmd3), Bash ne rapporte que le statut de sortie de la dernière commande (cmd3). Si cmd1 échoue, le script pourrait continuer à s'exécuter avec succès.
set -o pipefail garantit que le statut de sortie du pipeline est le statut de sortie de la dernière commande qui a échoué, ou zéro si toutes les commandes ont réussi. Ceci est essentiel pour un traitement fiable des données.
En-tête Standard du Mode Strict
Commencez toujours les scripts avancés avec cet en-tête robuste :
#!/bin/bash
set -euo pipefail
Certains modèles plus anciens définissent également IFS=$'\n\t'. Utilisez-le uniquement lorsque vous comprenez comment il affecte la division des mots dans le reste du script. Citer les variables et lire l'entrée avec while IFS= read -r line est généralement plus clair.
Vérification Conditionnelle des Erreurs
Bien que set -e gère les erreurs inattendues, vous devez souvent vérifier des conditions spécifiques ou fournir des messages d'erreur personnalisés.
Utilisation des Instructions if et des Fonctions Personnalisées
Au lieu de compter uniquement sur set -e, utilisez des blocs if pour gérer les défaillances potentielles connues avec élégance et fournir une sortie descriptive.
# Définir une fonction d'erreur personnalisée pour la cohérence
error_exit() {
printf '[FATAL] %s\n' "$1" >&2
exit 1
}
REP_TEMP="/tmp/traitement_donnees_$(date +%s)"
# Vérifier si la création du répertoire a réussi
if ! mkdir -p "$REP_TEMP"; then
error_exit "Échec de la création du répertoire temporaire : $REP_TEMP"
fi
echo "Répertoire temporaire créé avec succès : $REP_TEMP"
# Exemple de vérification de l'existence d'un fichier avant traitement
FICHIER_A_TRAITER="entree.csv"
if [[ ! -f "$FICHIER_A_TRAITER" ]]; then
error_exit "Fichier d'entrée introuvable : $FICHIER_A_TRAITER"
fi
Logique de Court-Circuit (&& et ||)
Pour les opérations séquentielles simples, utilisez les opérateurs de court-circuit. C'est très lisible et concis.
- Chaîne de Succès (
&&) : La deuxième commande ne s'exécute que si la première réussit. - Capture d'Échec (
||) : La deuxième commande ne s'exécute que si la première échoue.
# Exécuter la configuration puis traiter, échouer si la configuration échoue
configurer_environnement && traiter_donnees
# Essayer de se connecter, sinon quitter avec un message
essh user@server || { echo "Échec de la connexion, vérifiez les paramètres réseau." >&2; exit 2; }
Terminaison et Nettoyage Gracieux avec trap
La commande trap permet au script de capturer des signaux (comme Ctrl+C, la terminaison du système ou la sortie du script) et d'exécuter une commande ou fonction spécifiée avant de se terminer. Ceci est essentiel pour les tâches de nettoyage.
La Fonction cleanup
Définissez une fonction dédiée pour annuler toutes les modifications (par exemple, supprimer les fichiers temporaires, réinitialiser les configurations) et utilisez trap pour garantir qu'elle s'exécute quelle que soit la façon dont le script se termine.
# Variable globale pour que la fonction de nettoyage vérifie
FICHIER_TEMP=""
cleanup() {
printf '%s\n' "--- Exécution des Procédures de Nettoyage ---"
if [[ -f "$FICHIER_TEMP" ]]; then
rm -f "$FICHIER_TEMP"
echo "Fichier temporaire supprimé : $FICHIER_TEMP"
fi
# Optionnellement, fournir un rapport de statut de sortie final
}
# 1. Piège EXIT : Exécute le nettoyage quel que soit le succès, l'échec ou le signal.
trap cleanup EXIT
# 2. Piège les signaux (INT=Ctrl+C, TERM=Signal d'arrêt)
trap 'printf "%s\n" "Script interrompu par l'utilisateur ou un signal système." >&2; exit 130' INT
trap 'printf "%s\n" "Script terminé." >&2; exit 143' TERM
# --- Logique Principale du Script ---
FICHIER_TEMP=$(mktemp)
echo "Contenu temporaire" > "$FICHIER_TEMP"
# Si le script échoue ou est interrompu ici, cleanup() est garanti de s'exécuter
Pourquoi Utiliser trap cleanup EXIT?
Définir un trap sur EXIT garantit que la fonction de nettoyage s'exécutera que le script se termine normalement (exit 0), se termine explicitement avec une erreur (exit 1) ou soit forcé de se terminer en raison de set -e.
Rapport d'Erreur Avancé
Les messages d'erreur standard (commande introuvable) manquent souvent de contexte. Les scripts avancés doivent signaler ce qui a échoué, où cela a échoué et pourquoi.
Journalisation des Numéros de Ligne
Lorsqu'une fonction comme error_exit est appelée, vous pouvez déterminer le numéro de ligne dans le script où l'erreur s'est produite en utilisant le tableau BASH_LINENO ou la commande caller (bien que caller soit souvent restreint aux fonctions).
Pour un rapport simple en dehors des fonctions, utilisez la variable LINENO :
# Exemple de rapport d'échec immédiat
(commande_risquee) || {
echo "[ERREUR $LINENO] commande_risquee a échoué avec le statut $?" >&2
exit 3
}
Distinguer la Sortie
Envoyez toujours les messages informatifs vers la sortie standard (stdout) et les messages d'erreur/avertissement vers la sortie d'erreur standard (stderr). Ceci est crucial si la sortie de votre script est redirigée vers un autre programme ou journalisée en externe.
echo "Message informatif"(va versstdout)echo "[AVERTISSEMENT] Remplacement de configuration" >&2(va versstderr)
Mettre le Modèle Ensemble
Pour la plupart des scripts de production, le modèle pratique est simple : commencez par set -euo pipefail, validez les entrées avant de travailler, enveloppez les échecs attendus dans if ! commande; then ...; fi, et ajoutez trap cleanup EXIT avant de créer un état temporaire.
Cela vous donne des échecs utiles au lieu d'échecs mystérieux. La prochaine fois qu'un travail échoue à 2 heures du matin, le journal devrait montrer ce qui a échoué, où chercher et si le nettoyage a été exécuté.