Aller au contenu principal

Gestion d'erreurs avancée : pipefail, trap et nettoyage

Notions théoriques

Le piège des pipelines avec set -e

Vous connaissez set -eu depuis le niveau 1. Mais set -e a un angle mort avec les pipelines (|), vus en séance 4 : le code de retour d'une chaîne cmd1 | cmd2 est celui de cmd2 uniquement.

false | true
echo $? # 0 : true a réussi, l'échec de false est invisible

Résultat : set -e ne détecte pas l'échec de false dans cet exemple, puisque true (la dernière commande du pipeline) a réussi. L'option set -o pipefail, annoncée en fin de niveau 1, corrige cela : le pipeline échoue dès que n'importe laquelle de ses commandes échoue.

set -o pipefail
false | true
echo $? # 1 : l'échec de false est maintenant détecté

La ligne d'en-tête recommandée à partir de maintenant devient donc :

set -euo pipefail

Les angles morts restants de set -e

Même avec set -euo pipefail, certaines situations n'arrêtent pas le script :

  • une commande testée dans un if, while, ou après &&/|| — c'est voulu, sinon aucun test ne serait possible ;
  • une commande suivie explicitement de || true, qui neutralise volontairement son échec.
# Ne s'arrête PAS, même sous set -e : c'est le comportement attendu
if grep -q "erreur" fichier.log; then
echo "Erreur trouvée"
fi

# Échec volontairement ignoré
rm -f fichier_optionnel.tmp || true
attention

set -e reste un filet de sécurité pour les échecs inattendus, pas un substitut à une gestion d'erreur réfléchie avec des tests explicites — un point déjà souligné en séance 7 du niveau 1, qui garde toute sa valeur ici.

trap : réagir à un signal

trap exécute des instructions lorsqu'un signal particulier est reçu par le script :

trap 'instructions' SIGNAL
SignalDéclenché quand...
EXITle script se termine, quelle qu'en soit la raison (fin normale, exit, erreur)
INTl'utilisateur appuie sur Ctrl+C
TERMle script reçoit une demande d'arrêt (kill)
ERRune commande échoue (utile combiné à set -e)

Le signal EXIT est le plus utile en pratique : il garantit qu'un bloc de nettoyage s'exécute toujours, même en cas d'erreur imprévue ou d'interruption.

cleanup() {
rm -rf "$TMPDIR"
}
TMPDIR=$(mktemp -d)
trap cleanup EXIT
astuce

Placez toujours le trap juste après la création de la ressource à nettoyer : de cette façon, même si le script s'arrête à la ligne suivante (une erreur, par exemple), le nettoyage est déjà armé.

Créer des fichiers temporaires en sécurité

tmp=$(mktemp) # un fichier temporaire unique
tmpdir=$(mktemp -d) # un dossier temporaire unique

mktemp garantit un nom unique (pas de conflit si le script tourne deux fois en même temps) et crée le fichier avec des permissions restreintes. C'est nettement plus sûr qu'un nom fixe comme /tmp/monscript.tmp, qui pourrait déjà exister ou être manipulé par un autre processus.

Empêcher deux exécutions simultanées

Un script planifié (nous verrons cron en séance 7) peut se chevaucher avec sa propre exécution précédente si celle-ci dure trop longtemps. Un verrou l'empêche :

LOCK="/tmp/sauvegarde.lock"

if ! mkdir "$LOCK" 2>/dev/null; then
echo "Une exécution est déjà en cours" >&2
exit 3
fi
trap 'rmdir "$LOCK"' EXIT
info

mkdir échoue si le dossier existe déjà, et cette opération est atomique (aucun autre processus ne peut créer le même dossier entre le test et la création) — c'est ce qui en fait un verrou fiable, contrairement à un simple test [ -f fichier_verrou ] suivi d'une création, qui laisse une petite fenêtre où deux scripts pourraient passer le test en même temps. flock est une alternative plus robuste encore, à découvrir si le besoin s'en fait sentir.

Journaliser proprement

exec >> "$LOG" 2>&1

Cette ligne, placée en tête de script, redirige toute la sortie standard et d'erreur du reste du script vers le fichier $LOG, sans avoir à répéter >> "$LOG" sur chaque ligne. Pour afficher à la fois à l'écran et dans le fichier, tee -a (ajout) est plus adapté :

echo "Sauvegarde démarrée" | tee -a "$LOG"
info

logger "message" envoie un message vers le journal système (syslog/journald), consultable ensuite avec journalctl. Une alternative à un fichier de log dédié, utile pour un script destiné à un environnement de production surveillé de façon centralisée.

Localiser une erreur précisément

trap 'echo "Erreur à la ligne $LINENO" >&2' ERR

$LINENO contient le numéro de ligne en cours d'exécution — combiné à un trap ERR, cela permet de savoir exactement où un script s'est arrêté sans avoir à ajouter des echo de débogage partout.

Exemple pratique

#!/bin/bash
# Script : traitement.sh
# Rôle : démontrer trap, mktemp et un verrou d'exécution
set -euo pipefail

log_info() {
echo "[$(date +%H:%M:%S)] $1"
}

cleanup() {
log_info "Nettoyage de $TMPDIR"
rm -rf "$TMPDIR"
}

LOCK="/tmp/traitement.lock"
if ! mkdir "$LOCK" 2>/dev/null; then
echo "Une exécution est déjà en cours" >&2
exit 3
fi
trap 'rmdir "$LOCK"' EXIT

TMPDIR=$(mktemp -d)
trap cleanup EXIT
trap 'log_info "Interrompu par l'"'"'utilisateur"; exit 130' INT

log_info "Travail en cours dans $TMPDIR..."
sleep 10
log_info "Terminé"
astuce

Lancez ce script puis appuyez sur Ctrl+C avant la fin des 10 secondes : le message d'interruption s'affiche, et $TMPDIR disparaît quand même — le trap EXIT s'exécute même sur une sortie provoquée par un signal.

Test de mémorisation/compréhension


Que corrige set -o pipefail ?


Quel signal trap garantit l'exécution d'une fonction de nettoyage même en cas d'erreur inattendue ?


Pourquoi préférer mktemp -d à un nom de dossier fixe dans /tmp ?


Que fait commande || true sous set -e ?


TP pour réfléchir et résoudre des problèmes

Nous rendons sauvegarde.sh réellement robuste face aux erreurs, en reprenant la version structurée en fonctions du niveau 1.

Étape 1 — Passer à set -euo pipefail


Bonne pratique - pipefail dès qu'un script utilise des pipelines

Dès qu'un script combine des commandes avec | (ce qui est fréquent dès la séance 4), pipefail devrait accompagner systématiquement set -eu.

Étape 2 — Créer un espace de travail temporaire nettoyé automatiquement


Bonne pratique - trap juste après la création de la ressource

Armez le trap immédiatement après mktemp -d, avant toute autre instruction : si une erreur survient juste après, le nettoyage reste garanti.

Étape 3 — Ajouter un verrou d'exécution


Bonne pratique - mkdir comme verrou atomique

mkdir échoue si le dossier existe déjà, sans laisser de fenêtre où deux processus pourraient croire, tous les deux, avoir obtenu le verrou — contrairement à un test suivi d'une création en deux étapes séparées.

Étape 4 — Rediriger toute la sortie vers un fichier de log


Bonne pratique - Un exec en tête plutôt qu'une redirection répétée

exec >> "$LOG" 2>&1 en une seule ligne évite de répéter >> "$LOG" sur chaque echo du script, et garantit qu'aucun message ne peut être oublié par erreur.

📌 Une solution