Un script qui continue après une erreur ne gère pas les erreurs : il les ignore. Sans set -euo pipefail, un mv qui échoue affiche quand même “OK”. Sans read -r, un backslash dans un nom de fichier disparaît silencieusement. Quelques lignes de protection suffisent à transformer un script fragile en script fiable en production.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Activer
set -euo pipefailpour arrêter le script dès la première erreur - Utiliser
read -ret citer les variables pour éviter le word splitting - Valider les entrées avec des tests
[[ ]]avant chaque action destructive - Intercepter les erreurs imprévues avec
trap - Créer des fichiers temporaires sécurisés avec
mktemp - Analyser et corriger un script avec shellcheck
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Un script fonctionnel en développement peut silencieusement échouer en production — là où les chemins diffèrent, les variables sont absentes, et personne ne regarde la sortie d’erreur :
- un script de sauvegarde qui continue après un
mvraté et marque l’opération comme réussie - un script de déploiement qui lit
$CONFIG_FILEvide parce que la variable n’a pas été exportée - un pipeline
cat log | grep ERR | wc -lqui retourne 0 même sicata échoué - un fichier temporaire prévisible dans
/tmpremplacé par un lien symbolique avant l’écriture - un mot de passe en clair dans un script versionné sur GitHub
Les techniques de ce guide s’appliquent à tout script destiné à tourner en production, en cron, ou dans une CI — dès que vous n’êtes pas là pour observer la sortie.
Un script sans protections — le problème
Section intitulée « Un script sans protections — le problème »Voici deplacement.sh, un script simple mais truffé de pièges :
#!/bin/bashecho "Fichier source :"read src
echo "Fichier destination :"read dst
mv $src $dstecho "Déplacement effectué."Quatre problèmes identifiables :
| Problème | Conséquence |
|---|---|
read sans -r | Les backslashes sont interprétés : \n devient un saut de ligne |
$src et $dst sans guillemets | Un espace dans le nom scinde l’argument en plusieurs mots |
| Pas de validation | Si le fichier source n’existe pas, mv échoue mais “Déplacement effectué.” s’affiche quand même |
Pas de set -e | Un code de retour non nul ne stoppe pas l’exécution |
La suite corrige ces quatre problèmes.
set -euo pipefail
Section intitulée « set -euo pipefail »Placez toujours cette ligne juste après le shebang :
set -euo pipefail-e : s’arrêter à la première erreur
Section intitulée « -e : s’arrêter à la première erreur »Sans -e, un échec passe inaperçu :
#!/bin/bashrm /fichier/inexistantecho "Nettoyage terminé." # s'affiche quand mêmerm: cannot remove '/fichier/inexistant': No such file or directoryNettoyage terminé.Avec set -e, le script s’arrête après rm et n’affiche pas le message trompeur.
-u : interdire les variables non définies
Section intitulée « -u : interdire les variables non définies »set -uecho "Répertoire : $REPERTOIRE"bash: REPERTOIRE: unbound variableSans -u, la variable vide serait substituée silencieusement — potentiellement dangereux dans rm -rf "$dir/".
-o pipefail : détecter les erreurs dans les pipelines
Section intitulée « -o pipefail : détecter les erreurs dans les pipelines »Par défaut, seule la dernière commande d’un pipeline détermine le code de retour :
cat fichier_inexistant.txt | grep "motif"echo $? # retourne 1 (grep sans résultats), mais l'échec de cat est ignoréAvec set -o pipefail, le pipeline retourne le code du premier échec.
Valider et citer les variables
Section intitulée « Valider et citer les variables »read -r et guillemets doubles
Section intitulée « read -r et guillemets doubles »read -r préserve les backslashes tels quels. Les guillemets doubles protègent contre le word splitting et le globbing :
read -r src # -r : backslashes préservésmv "$src" "$dst" # guillemets : un seul argument même avec des espacesSans guillemets, si src="mon rapport.txt" alors mv $src $dst passe trois arguments à mv : mon, rapport.txt et la valeur de $dst.
Tester avant d’agir
Section intitulée « Tester avant d’agir »Validez chaque entrée avant toute opération destructive :
[[ -z "$src" ]] && erreur "Fichier source non spécifié."[[ -f "$src" ]] || erreur "Le fichier '$src' n'existe pas."
dst_dir=$(dirname "$dst")[[ -w "$dst_dir" ]] || erreur "Pas les droits d'écriture dans '$dst_dir'."Tests courants :
| Test | Signification |
|---|---|
-z "$var" | Variable vide ou non définie |
-n "$var" | Variable non vide |
-f "$chemin" | Fichier ordinaire existant |
-d "$chemin" | Répertoire existant |
-w "$chemin" | Accessible en écriture |
-r "$chemin" | Accessible en lecture |
trap — intercepter les signaux
Section intitulée « trap — intercepter les signaux »trap exécute une commande quand un signal ou un pseudo-signal est reçu.
Intercepter les erreurs inattendues (ERR)
Section intitulée « Intercepter les erreurs inattendues (ERR) »erreur() { echo "Erreur : $1" >&2 exit 1}
trap 'erreur "Interruption inattendue à la ligne $LINENO."' ERRQuand une commande échoue avec set -e actif, trap ERR s’exécute avant la sortie — utile pour afficher un message contextualisé avec le numéro de ligne.
Nettoyer à la sortie (EXIT)
Section intitulée « Nettoyer à la sortie (EXIT) »trap ... EXIT s’exécute toujours, quelle que soit la cause de la sortie (succès, erreur, Ctrl+C) :
tmpfile=$(mktemp)trap 'rm -f "$tmpfile"' EXIT
# Le fichier est supprimé même si le script échoue en cours de routemktemp — fichiers temporaires sécurisés
Section intitulée « mktemp — fichiers temporaires sécurisés »Ne jamais créer un fichier temporaire avec un nom prévisible (/tmp/monscript.tmp) : un attaquant peut créer un lien symbolique avant vous vers un fichier sensible.
tmpfile=$(mktemp) # Crée /tmp/tmp.xK3pQr (nom unique, permissions 600)tmprepertoire=$(mktemp -d) # Crée un répertoire temporaireAssocié à trap EXIT, la suppression est garantie même en cas d’erreur :
tmpfile=$(mktemp)trap 'rm -f "$tmpfile"' EXIT
grep "ERR\|WARN" /var/log/syslog > "$tmpfile"echo "$(wc -l < "$tmpfile") alertes trouvées"Pas de secrets en dur
Section intitulée « Pas de secrets en dur »Ne jamais coder un mot de passe ou une clé directement dans le script :
# À ne pas faireDB_PASSWORD="monsecret"mysql -u root -p"$DB_PASSWORD"Avec une variable d’environnement — définie avant l’appel :
# Dans le script : on lit la variable d'environnementmysql -u root -p"$DB_PASSWORD"# Avant d'exécuter le scriptexport DB_PASSWORD="monsecret"./mon_script.shAvec un fichier de configuration à accès restreint :
# config.conf — contient les secrets, jamais versionnéDB_PASSWORD="monsecret"chmod 600 config.confsource config.confmysql -u root -p"$DB_PASSWORD"Le script complet
Section intitulée « Le script complet »Voici deplacement_robuste.sh, version corrigée de deplacement.sh :
#!/bin/bashset -euo pipefail
# ── Gestion des erreurs ────────────────────────────────────────────────────────erreur() { echo "Erreur : $1" >&2 exit 1}
trap 'erreur "Interruption inattendue à la ligne $LINENO."' ERR
# ── Lectures sécurisées ────────────────────────────────────────────────────────echo "Fichier source :"read -r src
echo "Fichier destination :"read -r dst
# ── Validations ────────────────────────────────────────────────────────────────[[ -z "$src" ]] && erreur "Fichier source non spécifié."[[ -z "$dst" ]] && erreur "Fichier destination non spécifié."[[ -f "$src" ]] || erreur "Le fichier '$src' n'existe pas."
dst_dir=$(dirname "$dst")[[ -w "$dst_dir" ]] || erreur "Pas les droits d'écriture dans '$dst_dir'."
# ── Action ────────────────────────────────────────────────────────────────────mv -- "$src" "$dst"echo "OK : '$src' déplacé vers '$dst'."exit 0Le double tiret -- avant les arguments de mv protège contre les noms de fichiers commençant par -.
shellcheck et shfmt
Section intitulée « shellcheck et shfmt »shellcheck analyse statiquement un script et signale les mauvaises pratiques avant l’exécution :
sudo apt install shellcheck # Debian/Ubuntushellcheck deplacement.shIn deplacement.sh line 7:read src ^-- SC2162: read without -r will mangle backslashes.
In deplacement.sh line 13:mv $src $dst ^---^ SC2086: Double quote to prevent globbing and word splitting.Chaque code (SC2162, SC2086) pointe vers une explication détaillée sur shellcheck.net/wiki/SCxxxx.
shfmt reformate le code source de manière cohérente :
sudo apt install shfmtshfmt -i 4 -w mon_script.sh # indentation 4 espaces, réécriture en placeshfmt -d mon_script.sh # affiche les différences sans modifierDépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
| Le script s’arrête à une commande bénigne | -e interrompt sur tout code non nul | Protéger avec cmd || true ou if cmd; then ... fi |
unbound variable sur une variable initialisée | -u détecte un nom mal orthographié | Vérifier l’orthographe exacte et initialiser : var="" |
trap ERR ne se déclenche pas | -e non activé ou trap placé après l’erreur | Mettre set -euo pipefail et trap en tête de script |
mktemp: failed to create file | /tmp plein ou monté en readonly | df /tmp puis nettoyer les fichiers orphelins |
| Script fonctionne en interactif mais pas en cron | PATH différent dans cron | Utiliser les chemins complets (/bin/mv, /usr/bin/grep) |
À retenir
Section intitulée « À retenir »set -euo pipefailest la première ligne après le shebang dans tout script de production.read -rpréserve les backslashes ; les guillemets doubles protègent contre le word splitting.- Valider toujours les entrées avant une opération destructive (
mv,rm,cp). trap 'cmd' ERRintercepte les erreurs inattendues ;trap 'cmd' EXITgarantit le nettoyage.mktempgénère un nom unique sécurisé — ne jamais utiliser/tmp/monscript.tmp.- shellcheck détecte la plupart des erreurs avant l’exécution — à lancer avant chaque commit.