Maîtrise du débogage de scripts Bash : techniques essentielles pour les développeurs

Déboguez les scripts Bash avec des vérifications syntaxiques, xtrace, le mode strict, les traps, ShellCheck et une journalisation ciblée.

Maîtrise du débogage de scripts Bash : techniques essentielles pour les développeurs

Le débogage de scripts Bash commence par une question : où le script a-t-il fait quelque chose de différent de ce que vous attendiez ? Un bon débogage vous donne une visibilité sur les erreurs de syntaxe, les variables développées, l'ordre des commandes et les codes de sortie sans transformer le script en bruit.

Ce guide présente des techniques pratiques de débogage Bash que vous pouvez utiliser sur un script d'automatisation réel avant qu'il n'atteigne cron, CI ou la production.

Commencez par une vérification syntaxique

Avant de tracer le comportement à l'exécution, assurez-vous que Bash peut analyser le fichier :

bash -n ./deploy.sh

bash -n lit le script et signale les erreurs de syntaxe sans exécuter les commandes. Il détecte les fi, done, then, guillemets et accolades manquants. Il ne détectera pas les erreurs logiques, les fichiers manquants ou les commandes qui échouent à l'exécution.

Par exemple, cette faute de frappe est détectée avant que quoi que ce soit ne s'exécute :

if [ -f "$CONFIG" ]; then
    echo "Config trouvée"
# fi manquant

Exécutez une vérification syntaxique après de grandes modifications et avant d'ajouter plus de sorties de débogage.

Tracez l'exécution avec set -x

Le débogueur intégré le plus utile est xtrace :

set -x
some_command "$VALUE"
set +x

Avec le traçage activé, Bash imprime chaque commande après les expansions et avant l'exécution. Cela vous aide à voir si une variable est vide, si un glob s'est développé ou si une commande a reçu des arguments différents de ceux attendus.

Pour un traçage complet du script, exécutez :

bash -x ./deploy.sh

Pour des traces plus propres, définissez PS4 pour que chaque ligne inclue le numéro de ligne source :

export PS4='+ ${BASH_SOURCE}:${LINENO}: '
bash -x ./deploy.sh

Si votre script gère des secrets, ne tracez pas les sections qui impriment des jetons, mots de passe ou URL signées. Désactivez le traçage avant ces commandes :

set +x
login_with_secret "$API_TOKEN"
set -x

Ajoutez le mode strict avec précaution

Ces options détectent les échecs courants plus tôt :

set -euo pipefail

set -e quitte en cas d'échec de commande non géré. set -u traite les variables non définies comme des erreurs. set -o pipefail fait échouer un pipeline si une commande du pipeline échoue, pas seulement la dernière commande.

Elles sont utiles, mais ne remplacent pas une gestion explicite. Des commandes comme grep peuvent renvoyer 1 pour un résultat normal "non trouvé" :

if grep -q "READY" status.txt; then
    echo "prêt"
else
    echo "pas prêt"
fi

C'est plus clair que de masquer le résultat avec grep -q "READY" status.txt || true.

Imprimez les bonnes valeurs

Une journalisation ciblée est plus efficace que des lignes echo dispersées. Imprimez les valeurs qui affectent la branche que vous déboguez :

printf 'DEBUG: user=%q env=%q target=%q\n' "$USER_NAME" "$ENVIRONMENT" "$TARGET_HOST" >&2

printf '%q' affiche les valeurs échappées par le shell, ce qui facilite la détection des espaces et des caractères spéciaux. Envoyez la sortie de débogage vers stderr pour que la sortie normale du script reste utilisable dans les pipelines.

Lorsqu'une commande échoue, capturez son statut immédiatement :

run_migration
status=$?

if [ "$status" -ne 0 ]; then
    echo "La migration a échoué avec le code de sortie $status" >&2
    exit "$status"
fi

N'exécutez pas une autre commande avant de sauvegarder $?, car même echo le remplace.

Déboguez les boucles et les conditionnelles

Les bugs de boucle proviennent souvent de la division de mots ou d'une entrée inattendue. Citez les variables et lisez les lignes en toute sécurité :

while IFS= read -r line; do
    printf 'line=%q\n' "$line" >&2
done < input.txt

Pour les conditionnelles, imprimez les valeurs exactes comparées :

printf 'expected=%q actual=%q\n' "$EXPECTED" "$ACTUAL" >&2

if [[ "$ACTUAL" == "$EXPECTED" ]]; then
    echo "correspondance"
fi

Si vous devez faire une pause dans un script pendant le débogage local, read fonctionne :

read -r -p "Appuyez sur Entrée pour continuer..."

Supprimez les pauses avant de valider le script, surtout s'il peut s'exécuter sans surveillance.

Utilisez ShellCheck pour l'analyse statique

ShellCheck détecte de nombreux problèmes que Bash exécutera joyeusement jusqu'à ce qu'ils échouent dans un cas particulier :

shellcheck ./deploy.sh

Il signale les variables non citées, le code inaccessible, les tests suspects, les variables inutilisées et les problèmes de portabilité. Traitez les avertissements comme des invitations à inspecter le code, pas comme une preuve automatique que le script est erroné. Parfois, vous pouvez intentionnellement désactiver un avertissement, mais ajoutez un court commentaire expliquant pourquoi.

Utilisez trap pour voir la ligne en échec

Pour les scripts plus longs, un trap d'erreur peut vous indiquer où un échec s'est produit :

set -Eeo pipefail

trap 'echo "Erreur à la ligne $LINENO : $BASH_COMMAND" >&2' ERR

set -E aide le trap ERR à se propager dans les fonctions et sous-shells en Bash. C'est utile dans les logs CI où vous n'avez peut-être pas de shell interactif.

Conclusion

Commencez par bash -n, utilisez bash -x ou un set -x ciblé pour le traçage à l'exécution, et ajoutez une journalisation ciblée sur stderr autour de la branche qui se comporte incorrectement. Pour les scripts importants, exécutez ShellCheck et ajoutez un trap ERR pour que les échecs pointent vers la commande et la ligne qui nécessitent une attention.