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, notamment en examen RHCSA où il faut diagnostiquer rapidement.
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 - en examen RHCSA, un script vous est fourni avec un bug à corriger sous contrainte de temps — vous devez localiser le problème en moins de 2 minutes
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.
# 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.
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, avant exécution. Le préfixe + marque le niveau d’imbrication.
$ 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 :
#!/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 + dans la trace indique la profondeur d’appel — ++ pour une sous-fonction, +++ pour un sous-shell.
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 la ligne :
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 redirige la trace vers un descripteur de fichier séparé :
#!/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+ set +xCette 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 :
#!/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 tracer 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 (trop verbeux).
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 :
$ shellcheck mon_script.shExemples de problèmes détectés :
# SC2086 : variable non quotée → word splitting + globbingrm $fichier # ✗rm "$fichier" # ✓
# SC2046 : résultat de $() non quotécp $(ls *.log) /backup/ # ✗cp ./*.log /backup/ # ✓ — ou via tableau
# SC2068 : tableau passé sans guillemets doublescmd $tableau[@] # ✗cmd "${tableau[@]}" # ✓
# SC2155 : déclaration + affectation combinées avec locallocal val=$(commande) # ✗ — masque le code de retourlocal val; val=$(commande) # ✓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 est 0 — ce qui déclenche set -e :
set -en=0(( n++ )) # résultat = 0 → exit code 1 → set -e stoppe le script !echo "n=$n" # jamais exécutéSolution :
set -en=0n=$((n + 1)) # exit code 0 quelle que soit la valeurecho "n=$n" # n=1Même règle pour les tests arithmétiques :
# À éviter avec set -e si le résultat peut être 0(( a == 0 )) && echo "zéro"
# Préférerif (( a == 0 )); then echo "zéro"; fi# ou[[ $a -eq 0 ]] && echo "zéro"SIGPIPE avec set -e et pipes
Section intitulée « SIGPIPE avec set -e et pipes »Quand head ou grep ferme un pipe avant que la commande amont ait fini, elle reçoit SIGPIPE (code 141). Avec set -e, le script s’arrête :
# Problèmeset -efor i in {1..100}; do echo $i; done | head -5# Possible : exit code 141 → set -e stoppe
# Solution : utiliser un compteur interneset -ecount=0for i in {1..100}; do echo "$i" count=$((count + 1)) [[ $count -ge 5 ]] && breakdoneVariables vides avec set -u
Section intitulée « Variables vides avec set -u »set -u stoppe le script sur toute variable non définie :
set -uecho "$undefined" # bash: undefined: unbound variable → exit 1Pour fournir une valeur par défaut sans 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 »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 »| 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)).