Déboguer un script Bash commence toujours par la même question : est-ce un problème de syntaxe, de logique ou d'environnement ? Bash propose plusieurs niveaux de diagnostic, de la validation syntaxique (bash -n) à la trace ligne par ligne (bash -x), en passant par l'analyse statique (shellcheck) et les pièges réactifs (trap ERR). Choisir le bon outil pour le bon niveau d'erreur fait gagner un temps considérable. Les sorties de cette page ont été mesurées sur Bash 5.2.37 et shellcheck 0.10 (Debian 13).
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Valider la syntaxe d'un script sans l'exécuter avec
bash -n - Activer une trace d'exécution ligne par ligne avec
bash -xetset -x/set +x - Personnaliser le préfixe de trace avec
PS4pour y inclure le nom du fichier et la ligne - Rediriger la trace vers un fichier séparé avec
BASH_XTRACEFD - Réagir aux erreurs avec
trap ERRet inspecter chaque instruction avectrap DEBUG - Analyser statiquement un script avec
shellcheck - Identifier les pièges classiques :
(( ))avecset -e, SIGPIPE,set -uet variables vides
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Le débogage Bash intervient dans plusieurs situations fréquentes :
- un script fonctionnel en local échoue silencieusement en production, la trace
bash -xrévèle où il dévie - un script présente une erreur de syntaxe après modification,
bash -nl'identifie sans exécuter le code - un script de déploiement continue après une commande en échec sans signal apparent,
trap ERRintercepte la rupture - un collègue vous remet un script non documenté à corriger,
shellcheckliste les problèmes automatiquement - un script écrit à la hâte doit être corrigé vite, par exemple sous la contrainte de temps d'un examen pratique
bash -n, vérifier la syntaxe sans exécuter
Section intitulée « bash -n, vérifier la syntaxe sans exécuter »bash -n lit le script, vérifie la syntaxe et s'arrête. Aucune commande n'est exécutée, et le code de retour vaut 2 en cas d'erreur (mesuré), ce qui le rend utilisable dans un contrôle automatique.
# Script avec une erreur de syntaxe (fi manquant)$ cat test.sh#!/bin/bashif truethen echo "ok"# fi manquant
$ bash -n test.shtest.sh: line 6: syntax error: unexpected end of file# Script syntaxiquement valide$ bash -n mon_script.sh && echo "Syntaxe OK"Syntaxe OKUtilisez bash -n avant toute exécution sur un serveur de production : c'est gratuit et sans risque. Il ne voit en revanche que la syntaxe, pas une commande mal nommée ni une variable vide.
bash -x, trace complète d'exécution
Section intitulée « bash -x, trace complète d'exécution »bash -x exécute le script et affiche chaque commande après expansion des variables, juste avant de l'exécuter. La trace part sur la sortie d'erreur, préfixée par +.
$ bash -x mon_script.sh+ a=42+ b=84+ echo 'Résultat : 84'Résultat : 84Chaque ligne préfixée par + est une commande réellement exécutée, les variables y sont déjà substituées. C'est la première chose à faire quand un script produit un résultat inattendu.
Activer la trace sur une portion du script
Section intitulée « Activer la trace sur une portion du script »set -x active la trace à partir d'un point, set +x la désactive : seule la section encadrée apparaît dans la sortie :
#!/bin/bashset -euo pipefail
# Code sans trace...prepare_env
# Activer la trace uniquement sur la section critiqueset -xresultat=$(traitement_complexe "$1")set +x
echo "Résultat : $resultat"Le nombre de + indique le niveau de substitution : chaque $( ) imbriqué en ajoute un. Un appel de fonction n'en ajoute pas, pas plus qu'un sous-shell ( ) écrit explicitement. Mesuré sur ce script de cinq lignes :
f() { echo "dans f"; }fx=$(echo sub)( echo "sous-shell" )y=$(echo $(echo double))+ f+ echo 'dans f'dans f++ echo sub+ x=sub+ echo sous-shellsous-shell+++ echo double++ echo double+ y=doubleLe echo de la fonction reste à un seul +, celui de la substitution passe à ++, et la substitution imbriquée à +++.
PS4, personnaliser le préfixe de trace
Section intitulée « PS4, personnaliser le préfixe de trace »Par défaut, PS4='+ '. Vous pouvez l'enrichir avec le nom du fichier et le numéro de ligne, grâce aux variables BASH_SOURCE et LINENO évaluées à chaque ligne de trace :
export PS4='[${BASH_SOURCE##*/}:${LINENO}] 'set -xmsg="bonjour"echo "$msg"set +xSortie :
[demo-debug.sh:35] msg=bonjour[demo-debug.sh:36] echo bonjourbonjour[demo-debug.sh:37] set +xChaque ligne de trace indique maintenant le fichier et le numéro de ligne, indispensable quand des fonctions provenant de plusieurs fichiers sourcés sont impliquées.
BASH_XTRACEFD, trace vers un fichier séparé
Section intitulée « BASH_XTRACEFD, trace vers un fichier séparé »Quand la trace se mélange aux sorties du script, la lisibilité en souffre. BASH_XTRACEFD désigne le descripteur de fichier qui reçoit la trace : exec {BASH_XTRACEFD}>debug.log en ouvre un, et range son numéro dans la variable elle-même :
#!/bin/bash# Rediriger la trace vers debug.logexec {BASH_XTRACEFD}>debug.log
set -xcalcul=$((6 * 7))resultat=$(date '+%Y%m%d')set +x
exec {BASH_XTRACEFD}>&- # fermer le descripteur
echo "calcul=$calcul, date=$resultat"# La sortie standard est propre ; debug.log contient la trace$ cat debug.log+ calcul=42++ date +%Y%m%d+ resultat=20260929+ set +xLa trace complète est bien dans debug.log, substitution date comprise, et la sortie standard reste propre. Cette technique est particulièrement utile pour les scripts exécutés en cron ou dans une CI, où la trace ne doit pas parasiter les logs applicatifs.
trap, réagir aux événements d'exécution
Section intitulée « trap, réagir aux événements d'exécution »trap ERR, capturer les erreurs
Section intitulée « trap ERR, capturer les erreurs »trap 'commande' ERR exécute commande à chaque fois qu'une commande se termine avec un code non nul, avec ou sans set -e (mesuré). Avec set -e, le gestionnaire s'exécute juste avant la sortie :
#!/bin/bashset -euo pipefail
gestion_erreur() { echo "ERREUR à la ligne $1, code de sortie : $2" >&2 echo "Script abandonné." >&2}
trap 'gestion_erreur $LINENO $?' ERR
commande_qui_peut_echouertrap DEBUG, inspecter chaque instruction
Section intitulée « trap DEBUG, inspecter chaque instruction »trap 'commande' DEBUG exécute commande avant chaque instruction, utile pour compter les exécutions ou suivre l'état d'une variable :
#!/bin/bash# Afficher le numéro de ligne avant chaque instructiontrap 'echo " → ligne $LINENO"' DEBUG
x=1y=2z=$((x + y))
trap - DEBUG # désactiverecho "z=$z"En pratique, trap DEBUG sert surtout à instrumenter une section suspecte, pas à tout tracer : sur un script entier, la sortie devient vite illisible.
shellcheck, analyse statique
Section intitulée « shellcheck, analyse statique »shellcheck analyse un script sans l'exécuter et signale les erreurs, les pratiques dangereuses et les incompatibilités POSIX. Il s'installe avec apt install shellcheck sur Debian et Ubuntu, depuis EPEL (paquet ShellCheck) sur RHEL :
$ shellcheck mon_script.shVoici quatre défauts qu'il détecte, chacun en paire à éviter / correct. Les lignes marquées # à éviter sont des contre-exemples volontaires :
# SC2086 : variable non quotée, word splitting et globbingrm $fichier # à éviterrm "$fichier" # correct
# SC2046 : résultat de $() non quotécp $(ls *.log) /backup/ # à évitercp ./*.log /backup/ # correct, ou via un tableau
# SC2068 : tableau développé sans guillemets doublescmd ${tableau[@]} # à évitercmd "${tableau[@]}" # correct
# SC2155 : déclaration et affectation combinées avec locallocal val=$(commande) # à éviter : masque le code de retourlocal val; val=$(commande) # correctAttention à une variante fréquente du troisième cas : $tableau[@], sans accolades, n'est pas un tableau mal quoté mais une autre erreur. shellcheck la classe en SC1087 (accolades manquantes) et SC2128 : Bash ne développe que le premier élément, suivi du texte [@].
Pièges classiques
Section intitulée « Pièges classiques »(( )) avec set -e
Section intitulée « (( )) avec set -e »(( n++ )) retourne le code de sortie 1 quand le résultat de l'expression vaut 0, ce qui déclenche set -e. Avec n=0, le post-incrément vaut justement 0 :
set -en=0(( n++ )) # résultat = 0 → exit code 1 → set -e stoppe le script !echo "n=$n" # jamais exécutéLa solution est une affectation, dont le code de retour vaut toujours 0, quelle que soit la valeur calculée :
set -en=0n=$((n + 1)) # exit code 0 quelle que soit la valeurecho "n=$n" # n=1Les tests arithmétiques demandent plus de nuance. Au milieu d'un script, (( a == 0 )) && echo "zéro" n'arrête pas le script quand a vaut 1 : une commande testée par && est exemptée de set -e (mesuré). Le piège se déclenche quand cette ligne est la dernière d'une fonction : la fonction retourne alors 1, et son appel, lui, arrête le script.
set -ef() { (( a == 0 )) && echo "zéro"; } # retourne 1 quand a vaut 1a=1f # le script s'arrête ici, code 1echo "jamais affiché"La forme if (( a == 0 )); then echo "zéro"; fi évite le problème : un if dont la condition est fausse retourne 0, et la fonction avec lui.
SIGPIPE avec set -e et pipes
Section intitulée « SIGPIPE avec set -e et pipes »Quand head a lu ce qu'il voulait, il ferme le pipe ; la commande amont qui écrit encore reçoit SIGPIPE et sort avec le code 141. Deux conditions doivent être réunies pour que le script s'arrête, et la mesure le montre : pipefail doit être actif (sans lui, seul le code de head compte), et l'amont doit encore écrire quand head ferme. Cent lignes tiennent dans le tampon du pipe et ne déclenchent rien ; un million, si.
set -eo pipefailfor i in {1..100}; do echo "$i"; done | head -5 # continue : l'amont a déjà finiseq 1 1000000 | head -5 # le script s'arrête, code 141echo "jamais affiché"Le défaut est intermittent par nature : il dépend du volume et de la vitesse des deux côtés du pipe. Quand la troncature est voulue, neutralisez explicitement le code de ce seul pipeline :
set -eo pipefailseq 1 1000000 | head -5 || true # code 141 attendu, neutralisé ici seulementVariables vides avec set -u
Section intitulée « Variables vides avec set -u »set -u stoppe le script sur toute variable non définie, au lieu de la remplacer par une chaîne vide sans prévenir :
set -uecho "$undefined" # bash: undefined: unbound variable → exit 1Pour une variable réellement optionnelle, fournissez une valeur par défaut : l'expansion ${VAR:-défaut} ne déclenche pas l'erreur :
set -unom="${USER_NAME:-anonyme}" # OK même si USER_NAME n'est pas définiecho "Bonjour $nom"Guillemets et globbing
Section intitulée « Guillemets et globbing »Un chemin contenant un espace casse une commande dès que la variable n'est pas quotée. La correction consiste à quoter la variable et à laisser le glob hors des guillemets :
repertoire="/chemin/avec espaces"
# Problème : word splitting + globbingcp $repertoire/*.log /backup/
# Correct : guillemets doubles + glob explicitecp "$repertoire"/*.log /backup/Stratégie de débogage selon le type d'erreur
Section intitulée « Stratégie de débogage selon le type d'erreur »Le bon outil dépend du symptôme. Ce tableau résume la page : partez de ce que vous observez, et appliquez l'outil de la même ligne avant de passer au suivant.
| Symptôme | Outil recommandé | Action |
|---|---|---|
| Erreur de syntaxe au lancement | bash -n | Identifier la ligne exacte |
| Résultat inattendu | bash -x | Lire les expansions après + |
| Erreur dans une section précise | set -x / set +x | Encadrer la section |
| Plusieurs fichiers sourcés | PS4 avec BASH_SOURCE:LINENO | Localiser la ligne exacte |
| Script en CI / sans terminal | BASH_XTRACEFD | Séparer trace et sortie |
| Code de style et bugs connus | shellcheck | Analyse statique complète |
| Script s'arrête sans message | trap ERR | Afficher ligne + code de sortie |
À retenir
Section intitulée « À retenir »bash -nvalide la syntaxe sans exécuter, à utiliser systématiquement avant un déploiement.bash -xtrace chaque commande après expansion : c'est le premier réflexe face à un résultat inattendu.set -x/set +xciblent la trace sur une section précise du script.PS4='[${BASH_SOURCE##*/}:${LINENO}] 'rend la trace exploitable dans un script multi-fichiers.trap 'handler $LINENO $?' ERRcapture toutes les erreurs non gérées.shellcheckdétecte les problèmes courants avant l'exécution, intégrez-le dans votre workflow.- Avec
set -e, évitez(( n++ ))quandn=0, utilisezn=$((n + 1)).
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Personnaliser Bash en profondeur
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications