Aller au contenu
Administration Linux medium

Déboguer un script Bash : bash -x, set -x, shellcheck

12 min de lecture

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.

  • Valider la syntaxe d’un script sans l’exécuter avec bash -n
  • Activer une trace d’exécution ligne par ligne avec bash -x et set -x/set +x
  • Personnaliser le préfixe de trace avec PS4 pour 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 ERR et inspecter chaque instruction avec trap DEBUG
  • Analyser statiquement un script avec shellcheck
  • Identifier les pièges classiques : (( )) avec set -e, SIGPIPE, set -u et variables vides

Le débogage Bash intervient dans plusieurs situations fréquentes :

  • un script fonctionnel en local échoue silencieusement en production — la trace bash -x révèle où il dévie
  • un script présente une erreur de syntaxe après modification — bash -n l’identifie sans exécuter le code
  • un script de déploiement continue après une commande en échec sans signal apparent — trap ERR intercepte la rupture
  • un collègue vous remet un script non documenté à corriger — shellcheck liste 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 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/bash
if true
then
echo "ok"
# fi manquant
$ bash -n test.sh
test.sh: line 6: syntax error: unexpected end of file
Fenêtre de terminal
# Script syntaxiquement valide
$ bash -n mon_script.sh && echo "Syntaxe OK"
Syntaxe OK

Utilisez bash -n avant toute exécution sur un serveur de production — c’est gratuit et sans risque.

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.

Fenêtre de terminal
$ bash -x mon_script.sh
+ a=42
+ b=84
+ echo 'Résultat : 84'
Résultat : 84

Chaque 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.

set -x active la trace à partir d’un point, set +x la désactive :

#!/bin/bash
set -euo pipefail
# Code sans trace...
prepare_env
# Activer la trace uniquement sur la section critique
set -x
resultat=$(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.

Par défaut, PS4='+ '. Vous pouvez l’enrichir avec le nom du fichier et la ligne :

Fenêtre de terminal
export PS4='[${BASH_SOURCE##*/}:${LINENO}] '
set -x
msg="bonjour"
echo "$msg"
set +x

Sortie :

[demo-debug.sh:35] msg=bonjour
[demo-debug.sh:36] echo bonjour
bonjour
[demo-debug.sh:37] set +x

Chaque 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.

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.log
exec {BASH_XTRACEFD}>debug.log
set -x
calcul=$((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
Fenêtre de terminal
$ cat debug.log
+ calcul=42
+ set +x

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 'commande' ERR exécute commande à chaque fois qu’une commande se termine avec un code non-nul :

#!/bin/bash
set -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_echouer

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 instruction
trap 'echo " → ligne $LINENO"' DEBUG
x=1
y=2
z=$((x + y))
trap - DEBUG # désactiver
echo "z=$z"

En pratique, trap DEBUG sert surtout à instrumenter une section suspecte — pas à tout tracer (trop verbeux).

shellcheck analyse un script sans l’exécuter et signale les erreurs, les pratiques dangereuses et les incompatibilités POSIX :

Fenêtre de terminal
$ shellcheck mon_script.sh

Exemples de problèmes détectés :

Fenêtre de terminal
# SC2086 : variable non quotée → word splitting + globbing
rm $fichier # ✗
rm "$fichier" # ✓
# SC2046 : résultat de $() non quoté
cp $(ls *.log) /backup/ # ✗
cp ./*.log /backup/ # ✓ — ou via tableau
# SC2068 : tableau passé sans guillemets doubles
cmd $tableau[@] # ✗
cmd "${tableau[@]}" # ✓
# SC2155 : déclaration + affectation combinées avec local
local val=$(commande) # ✗ — masque le code de retour
local val; val=$(commande) # ✓

(( n++ )) retourne le code de sortie 1 quand le résultat de l’expression est 0 — ce qui déclenche set -e :

Fenêtre de terminal
set -e
n=0
(( n++ )) # résultat = 0 → exit code 1 → set -e stoppe le script !
echo "n=$n" # jamais exécuté

Solution :

Fenêtre de terminal
set -e
n=0
n=$((n + 1)) # exit code 0 quelle que soit la valeur
echo "n=$n" # n=1

Même règle pour les tests arithmétiques :

Fenêtre de terminal
# À éviter avec set -e si le résultat peut être 0
(( a == 0 )) && echo "zéro"
# Préférer
if (( a == 0 )); then echo "zéro"; fi
# ou
[[ $a -eq 0 ]] && echo "zéro"

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 :

Fenêtre de terminal
# Problème
set -e
for i in {1..100}; do echo $i; done | head -5
# Possible : exit code 141 → set -e stoppe
# Solution : utiliser un compteur interne
set -e
count=0
for i in {1..100}; do
echo "$i"
count=$((count + 1))
[[ $count -ge 5 ]] && break
done

set -u stoppe le script sur toute variable non définie :

Fenêtre de terminal
set -u
echo "$undefined" # bash: undefined: unbound variable → exit 1

Pour fournir une valeur par défaut sans erreur :

Fenêtre de terminal
set -u
nom="${USER_NAME:-anonyme}" # OK même si USER_NAME n'est pas défini
echo "Bonjour $nom"
Fenêtre de terminal
repertoire="/chemin/avec espaces"
# Problème : word splitting + globbing
cp $repertoire/*.log /backup/
# Correct : guillemets doubles + glob explicite
cp "$repertoire"/*.log /backup/
SymptômeOutil recommandéAction
Erreur de syntaxe au lancementbash -nIdentifier la ligne exacte
Résultat inattendubash -xLire les expansions après +
Erreur dans une section préciseset -x / set +xEncadrer la section
Plusieurs fichiers sourcésPS4 avec BASH_SOURCE:LINENOLocaliser la ligne exacte
Script en CI / sans terminalBASH_XTRACEFDSéparer trace et sortie
Code de style et bugs connusshellcheckAnalyse statique complète
Script s’arrête sans messagetrap ERRAfficher ligne + code de sortie
  • bash -n valide la syntaxe sans exécuter — à utiliser systématiquement avant un déploiement.
  • bash -x trace chaque commande après expansion : c’est le premier réflexe face à un résultat inattendu.
  • set -x / set +x ciblent 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 $?' ERR capture toutes les erreurs non gérées.
  • shellcheck détecte les problèmes courants avant l’exécution — intégrez-le dans votre workflow.
  • Avec set -e, évitez (( n++ )) quand n=0 — utilisez n=$((n + 1)).

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn