Introduction
Bash est la colle qui tient ensemble de nombreux jobs de build, de release et d'exploitation. Quand cela échoue, le rayon d'action peut être important. Ce guide est une référence hands-on pour diagnostiquer et corriger des problèmes Bash avec des extraits prêts à copier-coller. Vous y trouverez un workflow simple, quels Bash logs capturer, des Bash commands sûres qui évitent la perte de données, et des schémas de récupération pas à pas que vous pouvez exécuter localement, dans Docker et Docker Compose, et à l'intérieur de pods Kubernetes. Les mots-clés utiles (Bash troubleshooting, Bash errors, Bash recovery) sont utilisés naturellement tout au long du guide.
Aperçu du workflow
Suivez ce flux reproductible, du symptôme au correctif validé :
- Capturer le symptôme
- Notez la commande exacte, ses arguments, le répertoire courant et le code de sortie :
echo $?. - Sauvegardez stderr et stdout séparément :
cmd 1>out.log 2>err.log. - Enregistrez les versions :
bash --version,printf '%s\n' "$BASH_VERSION".
- Réduire l'échec
- Réduisez le script au plus petit extrait qui échoue encore.
- Remplacez les dépendances externes par des fakes ou des fixtures.
- Tracer et journaliser
- Activez le tracing uniquement autour des lignes suspectes :
{
set -x
# bloc suspect
do_thing
set +x
} 2>trace.log
- Ajoutez des timestamps pour les étapes lentes :
export PS4='+ ${EPOCHREALTIME} ${BASH_SOURCE}:${LINENO}: '
- Exécuter des vérifications de cause racine
- Vérifiez PATH, les guillemets, les permissions, les fins de ligne et le comportement des pipelines.
- Appliquer une correction sûre
- Utilisez des options non destructives, des dry-runs et des fichiers temporaires.
- Prouver et documenter
- Rejouez le repro minimal et le script original.
- Ajoutez un test ou un exemple pour éviter la régression.
Collecte des logs
De bons logs répondent à : quoi a échoué, où et pourquoi.
- Capture de base
# Code de sortie et stderr
failing_cmd 1>stdout.log 2>stderr.log; echo $? >exit.code
# Trace complète dans un descripteur séparé pour ne pas mélanger avec la sortie du programme
exec {XTRACE_FD}>trace.log
export BASH_XTRACEFD=$XTRACE_FD
export PS4='+ ${EPOCHREALTIME} ${BASH_SOURCE}:${LINENO}: '
set -x
failing_cmd
set +x
exec {XTRACE_FD}>&-
- Garder les secrets hors des logs
set +x # désactiver le tracing autour des secrets
token="${TOKEN:?missing TOKEN}"
set -x
- Logs de conteneur et de cluster
- Docker :
docker logs <container> 2>&1 | tee container.log - Compose :
docker compose logs svc -f - Kubernetes :
kubectl logs pod/my-pod -c my-container --timestamps
- Instantané d'environnement (éviter les secrets)
( env | grep -Ev 'KEY|SECRET|TOKEN|PASS' ) | sort >env.snapshot
Vérifications de cause racine
Ciblez d'abord les classes d'échecs courantes avant de deviner.
- PATH et résolution de commande
which mycmd || type -a mycmd || echo 'missing'
hash -r # vider le cache de hachage de Bash si les binaires ont bougé
printf '%s\n' "$PATH" | tr ':' '\n'
- Shebang et différences de shell
head -1 script.sh # devrait être : #!/usr/bin/env bash
bash -n script.sh # vérification de syntaxe
# Certaines distros font /bin/sh -> dash ; forcer bash si nécessaire
bash script.sh
- Permissions et exécutables
chmod +x script.sh
file script.sh # vérifier la présence de CRLF
# Si CRLF détecté, normaliser :
dos2unix script.sh # ou : sed -i 's/\r$//' script.sh
- Guillemets et globbing
# Expansion de variable sûre
echo "$var"
# Désactiver le globbing lors de l'expansion d'entrées utilisateur
set -f; printf '%s\n' $user_input; set +f
# Plus sûr : toujours citer
printf '%s\n' "$user_input"
- Pipelines et codes de sortie
set -o pipefail # échouer si une commande du pipeline échoue
# Capturer le statut du pipeline
foo | bar | baz; ec=$?; echo "pipeline exit=$ec"
# Si un non-match de grep est acceptable :
grep -q pattern file || true
- Variables non liées et valeurs par défaut
set -u # traiter les variables non définies comme des erreurs
# Fournir des valeurs par défaut ou des erreurs franches
name="${NAME:-anonymous}"
api="${API_URL:?API_URL is required}"
- Boucles sûres pour stdin
# Traiter des lignes en préservant les espaces et sans globbing
while IFS= read -r line; do
printf 'line: %s\n' "$line"
done < input.txt
- Fichiers temporaires et conditions de concurrence
f=$(mktemp) || exit 1
trap 'rm -f "$f"' EXIT
# utiliser "$f" en toute sécurité ici
Commandes sûres
Préférez les opérations non destructives pendant la récupération.
- Dry-runs et no-clobber
rsync -av --dry-run src/ dst/
cp -an src/ dst/ # -n no-clobber, -a préservation
mv -i old new # -i demande de confirmation
rm -i file # confirmer avant de supprimer
- Sauvegardes et points de contrôle
cp -a dir "dir.bak.$(date +%Y%m%d-%H%M%S)"
- Garde-fous
set -euo pipefail # attention : modifie la sémantique d'échec
# Réduire la portée à un bloc
do_safe() {
set -euo pipefail
"$@"
}
- Limitation de débit et timeouts
timeout 10s long_running || echo 'timed out'
Récupération pas à pas
Voici des playbooks pratiques à adapter.
1) Commande introuvable ou mauvais binaire
Symptômes : command not found ou une version inattendue s'exécute.
Vérifications
type -a tool
printf '%s\n' "$PATH" | tr ':' '\n'
hash -r
Correctifs
- Installer ou ajuster PATH dans le script :
export PATH=/opt/tool/bin:$PATH. - Utiliser des chemins absolus pour les binaires critiques.
- En conteneur, placer la dépendance dans l'image ou l'installer au démarrage.
2) Crash de variable non liée
Symptômes : unbound variable avec set -u.
Vérifications
set -u
: "${NAME:?NAME is required}"
Correctifs
- Fournir des valeurs par défaut ou des échecs explicites :
NAME="${NAME:-guest}"
API_URL="${API_URL:?required}"
3) Échec masqué par un pipeline
Symptômes : la dernière commande réussit mais une étape amont a échoué.
Vérification et correctif
set -o pipefail
foo | bar | baz
Si un retour non-zéro de grep sans correspondance est acceptable, ajoutez || true.
4) Problème de permission ou de shebang
Symptômes : Permission denied ou le script tourne sous sh au lieu de Bash.
Vérifications et correctifs
chmod +x script.sh
sed -n '1p' script.sh # attendre #!/usr/bin/env bash
bash -n script.sh
5) Fins de ligne CRLF cassent l'exécution
Symptômes : apparition de ^M ou scripts qui échouent dans des conteneurs Linux.
Correctif
file script.sh
dos2unix script.sh # ou sed -i 's/\r$//' script.sh
6) Bug de guillemets et expansion inattendue
Symptômes : noms de fichiers avec espaces qui cassent les boucles ou les cibles de rm.
Patron de correctif
# Toujours citer les expansions
rm -- "$file"
# find + xargs en toute sécurité
find . -type f -name '*.log' -print0 | xargs -0 rm -f --
7) Différences cron vs interactif
Symptômes : un script réussit en shell, échoue sous cron ou un job runner.
Vérifications
# Cron a un environnement minimal
( env | sort ) >interactive.env
( env -i bash -lc 'env' | sort ) >isolated.env
diff -u interactive.env isolated.env || true
Correctifs
- Définir PATH explicitement dans le script.
- Utiliser des chemins absolus pour les fichiers et binaires.
- Sourcer un profil connu si requis, ou exporter les variables nécessaires dans l'entrée crontab.
Spécificités Docker et Docker Compose
- Reproduire à l'intérieur de la même image
docker run --rm -it -v "$PWD":/work -w /work myimg bash -lc './script.sh'
docker exec -it mycontainer bash -lc 'type -a tool && ./script.sh'
- Vérifier propriétaire et permissions après des bind mounts.
- Normaliser les fins de ligne sur l'hôte avant de builder l'image.
- Shell sur un service Compose
docker compose run --rm svc bash -lc 'set -euxo pipefail; ./script.sh'
Spécificités Kubernetes (Pods, Ingress, Secrets)
- Exec dans le conteneur avec le bon shell
kubectl exec -it deploy/app -c app -- bash -lc 'type -a bash || cat /etc/os-release'
- Logs et redémarrages
kubectl logs deploy/app -c app --timestamps --previous
- Config et secrets
# Inspecter les fichiers montés sans afficher les valeurs dans l'historique
ls -l /etc/config /etc/secret
stat /etc/secret/*
# Éviter le tracing des expansions de secrets
set +x; KEY="${API_KEY:?missing}"; set -x
- Vérifications réseau utiles à Ingress et services
curl -sv http://svc.namespace.svc.cluster.local:8080/health || true
getent hosts example.com
nc -zv example.com 443 || true
Plan pilote local
Gardez votre première implémentation petite, mesurable et simple à inspecter localement.
- Choisir 3 échecs fréquents
- Exemple : variable non liée, lignes CRLF et échec masqué de pipeline.
- Créer des repros minimaux
# repro-unbound.sh
set -u; echo "Hello $NAME"
# repro-crlf.sh (volontairement CRLF)
printf 'echo win\r\n' > repro-crlf.sh
# repro-pipe.sh
false | grep x
- Ajouter un wrapper de trace et logs
run_traced() {
exec {XTRACE_FD}>trace.log
export BASH_XTRACEFD=$XTRACE_FD
export PS4='+ ${EPOCHREALTIME} ${BASH_SOURCE}:${LINENO}: '
set -x; "$@"; ec=$?; set +x
exec {XTRACE_FD}>&-
return "$ec"
}
run_traced bash repro-unbound.sh || true
- Mesurer le temps de correction
- Mesurez le temps nécessaire pour identifier et corriger avant et après l'ajout du wrapper et des vérifications.
- Un timer simple :
time bash repro-unbound.sh.
- Étendre aux conteneurs et aux clusters
- Une fois le pilote local stable, reproduisez dans votre image Docker et dans un pod Kubernetes, puis appliquez les mêmes vérifications et wrappers.
Conclusion
Le Bash troubleshooting s'accélère quand vous suivez un workflow clair, capturez les bons Bash logs, utilisez des Bash commands de récupération sûres et appliquez des playbooks éprouvés pour les échecs courants. Commencez par un petit pilote local, mesurez l'impact, puis étendez les mêmes schémas à Docker, Docker Compose et Kubernetes (Ingress, Secrets compris). Continuez d'enrichir vos exemples et snippets pour rendre les incidents futurs prévisibles et à faible risque.