Retour au cours

infra / linux-bash

Debugging bash avancé (set -x, shellcheck, strace)

Leçon 231 exercice

Explication

Un script qui ne fait pas ce qu'on attend échoue rarement avec un message clair : une variable vide, un espace manquant dans une condition [ ], ou un glob qui ne matche aucun fichier. Déboguer, c'est rendre visible ce que bash exécute réellement, pas ce qu'on croit qu'il exécute.

Ce que vous allez apprendre

  • Tracer l'exécution d'un script commande par commande avec set -x / bash -x
  • Personnaliser PS4 pour situer précisément une ligne dans un script avec plusieurs fonctions
  • Détecter les pièges classiques (quoting, cd non vérifié) avant l'exécution avec shellcheck
  • Diagnostiquer un programme externe qui bloque ou plante avec strace
  • Éviter le piège de $? qui ne renvoie que le code de la dernière commande d'un pipe

Dans quel contexte ?

Un script de déploiement, écrit par un collègue parti depuis, échoue de façon intermittente sur le serveur de production sans message d'erreur exploitable. Avant de le réécrire à l'aveugle, un ingénieur DevOps doit d'abord comprendre précisément ce qu'il fait réellement, ligne par ligne, avec les vraies valeurs des variables — exactement ce que permet bash -x script.sh, sans même avoir à modifier le fichier original.

Voici le premier outil : la trace d'exécution

set -x affiche chaque commande juste avant de l'exécuter, avec les variables déjà remplacées par leur valeur réelle. Lancer bash -x script.sh fait la même chose sans modifier le fichier, pratique pour déboguer un script que tu n'as pas écrit toi-même.

Une fois la trace activée, il reste un problème sur un script complexe

Avec plusieurs fonctions imbriquées, une trace brute devient vite illisible : on ne sait plus quelle ligne appartient à quelle fonction. Personnaliser PS4 avec export PS4='+ ${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]:-main}(): ' affiche le fichier, le numéro de ligne et le nom de la fonction devant chaque commande tracée.

OutilRépond àQuand l'utiliser
set -x / bash -xQue fait le script, ligne par ligne ?Comportement inattendu dans le script
shellcheckLe script contient-il un piège connu ?Avant même de lancer le script
strace -fPourquoi un programme externe bloque/plante ?Le script est correct, le problème vient d'ailleurs

Bonne pratique

Fais de shellcheck script.sh un réflexe systématique avant tout déploiement : il repère des pièges connus comme SC2086 (oublier les guillemets autour d'une variable) ou SC2164 (cd non vérifié). C'est un gain de temps considérable — autant corriger un bug dans l'éditeur qu'après un déploiement raté.

Il reste un cas que shellcheck ne couvre pas : un programme externe qui se comporte mal

Si le script lui-même est correct mais qu'un programme qu'il appelle bloque ou plante, strace -f ./mon_programme observe les appels système effectués (ouverture de fichiers, connexions réseau) et révèle souvent la vraie cause, comme un fichier de config introuvable.

Piège fréquent

$? après un pipe (cmd1 | cmd2 | cmd3) ne renvoie que le code de sortie de la DERNIÈRE commande de la chaîne. Un pipe peut donc sembler avoir "réussi" ($? = 0) alors qu'une étape intermédiaire a échoué. echo "${PIPESTATUS[@]}" juste après le pipe donne le code de sortie de chaque commande individuellement.

Maintenant que tu sais déboguer un script, la prochaine leçon regarde la santé globale du système qui l'exécute.

Commandes & code

Debugging bash avancé

bash
# --- set -x : trace chaque commande exécutée avant de la lancer ---
#!/usr/bin/env bash
set -x
mon_var="test"
echo "$mon_var"
set +x          # désactive le tracing pour la suite du script

# Activer le tracing uniquement autour d'une section suspecte
{
    set -x
    fonction_suspecte
    set +x
} 2>> debug.log

# Lancer un script déjà écrit avec tracing, sans le modifier
bash -x script.sh
bash -x script.sh 2> trace.log            # capture la trace dans un fichier

# Personnaliser le préfixe de trace (très utile avec plusieurs fonctions imbriquées)
export PS4='+ ${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]:-main}(): '
set -x

# --- shellcheck : analyse statique, détecte les pièges classiques ---
shellcheck script.sh
# Exemples d'avertissements typiques :
# SC2086: Double quote to prevent globbing and word splitting (oublier "$var")
# SC2046: Quote this to prevent word splitting
# SC2164: Use 'cd ... || exit' in case cd fails
shellcheck -x script.sh          # suit aussi les fichiers "source"d
shellcheck -S warning script.sh    # ignore les avertissements mineurs (style)

# --- strace : voir les appels système d'un programme (pourquoi ça bloque/plante) ---
strace ./mon_programme
strace -f ./mon_programme                # suit aussi les processus fils (fork/exec)
strace -e trace=network curl https://exemple.com    # filtre uniquement les appels réseau
strace -e trace=open,openat -p 1234        # attache à un process déjà lancé, filtre les fichiers ouverts
strace -c ./mon_programme                    # résumé statistique par appel système (perf)

# --- ltrace : équivalent pour les appels de bibliothèque (moins courant, souvent absent par défaut) ---
ltrace ./mon_programme

# --- Débogage d'une erreur de code de sortie ---
mon_script.sh; echo "code de sortie : $?"
# Vérifier PIPESTATUS après un pipe (le $? seul ne donne que le code de la DERNIÈRE commande)
cmd1 | cmd2 | cmd3
echo "${PIPESTATUS[@]}"          # code de sortie de CHAQUE commande du pipe

# --- Assertions et logs de debug conditionnels ---
DEBUG="${DEBUG:-0}"
debug_log() {
    [ "$DEBUG" = "1" ] && echo "[DEBUG] $*" >&2
}
DEBUG=1 ./script.sh          # active les logs de debug pour cette exécution uniquement

# Vérifier la syntaxe d'un script SANS l'exécuter
bash -n script.sh

Résumé

  • set -x (ou bash -x) trace chaque commande — combine-le avec PS4 pour situer précisément la ligne.
  • shellcheck attrape la majorité des bugs classiques (quoting, cd non vérifié) avant l'exécution.
  • strace/ltrace servent quand le problème vient d'un binaire externe et non du script lui-même.

Exercices pratiques

1 disponible
1

Mission : traquer un bug intermittent hérité

Objectif : Utiliser bash -x, PS4, PIPESTATUS et strace pour localiser une panne qu'un collègue parti n'a pas documentée.

Contexte

Un script deploy.sh, écrit par un collègue parti depuis, échoue de façon intermittente en production sans message exploitable. Tu dois le comprendre sans le réécrire à l'aveugle, et sans même le modifier pour commencer.

Résoudre l’exercice →