Introduction
Bash est la colle qui tient ensemble de nombreux jobs de build, de release et d’exploitation. Quand cela échoue, l’impact peut être large. Ce guide, entièrement pratique, fournit un workflow reproductible, des extraits prêts à l’emploi, des commandes sûres, des interprétations de résultats et des mesures de retour arrière. Vous pourrez l’appliquer localement, dans Docker et Docker Compose, et à l’intérieur de pods Kubernetes.
Prérequis et zone de sécurité
Avant tout dépannage, mettez en place un cadre qui évite les dégâts et facilite le retour arrière.
- Version de Bash et shell effectif
- Vérifiez la version et le shell utilisé :
bash --version
printf '%s\n' "$BASH_VERSION"
echo "$SHELL"; ps -o comm= -p $$
- À interpréter : assurez-vous d’exécuter Bash et non un autre shell (p. ex.
dash).
- Répertoire de travail et permissions
- Confirmez le répertoire courant et l’accessibilité :
pwd; id; umask; ls -la
- Zone de test et sauvegardes
- Travaillez sur une copie, jamais sur des données de production directement :
cp -a projet "projet.bak.$(date +%Y%m%d-%H%M%S)"
- Utilisez des options non destructives (
--dry-run,-n,-i) tant que le correctif n’est pas validé.
- Journalisation séparée et horodatée
- Préparez-vous à capturer
stdout,stderret une traceset -xdistincte.
Workflow de dépannage reproductible
Suivez ce flux, du symptôme au correctif validé et documenté.
- Capturer le symptôme
- Notez la commande exacte, ses arguments, le répertoire courant et le code de sortie :
failing_cmd arg1 arg2 1>out.log 2>err.log; echo $? > exit.code
- Enregistrez la version de Bash :
bash --version; printf '%s\n' "$BASH_VERSION"
- Réduire l’échec au plus petit repro
- Isolez le fragment minimal qui échoue encore.
- Remplacez les dépendances externes par des fakes/fixtures.
- Tracer et journaliser (granulaire)
- Tracez seulement autour des lignes suspectes :
{
exec {XTRACE_FD}>trace.log
export BASH_XTRACEFD=$XTRACE_FD
export PS4='+ ${EPOCHREALTIME} ${BASH_SOURCE}:${LINENO}: '
set -x
do_thing
set +x
exec {XTRACE_FD}>&-
} 2>/dev/null
- Évitez de tracer autour des secrets :
set +x; token="${TOKEN:?missing TOKEN}"; set -x
- Vérifier les causes racines probables
- Suivez la check-list ci-dessous (PATH, guillemets, permissions, fins de ligne, pipelines…).
- Appliquer une correction sûre
- Privilégiez les options non destructives, les
--dry-run, les fichiers temporaires (mktemp), et une portée limitée des garde-fous.
- Prouver et documenter
- Rejouez le repro minimal puis le script original.
- Conservez les artefacts (logs, trace, diff) et ajoutez un test/README pour éviter la régression.
Collecte des logs (claire et exploitable)
De bons logs répondent à quoi a échoué, où et pourquoi, sans secrets involontaires.
- Capture de base
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 fonctionnelle)
exec {XTRACE_FD}>trace.log
export BASH_XTRACEFD=$XTRACE_FD
export PS4='+ ${EPOCHREALTIME} ${BASH_SOURCE}:${LINENO}: '
set -x
failing_cmd || true
set +x
exec {XTRACE_FD}>&-
- Garder les secrets hors des logs
set +x
KEY="${API_KEY:?missing}"
set -x
- Logs de conteneur et de cluster
# Docker
docker logs mycontainer 2>&1 | tee container.log
# Docker Compose
docker compose logs svc -f
# Kubernetes
kubectl logs pod/my-pod -c my-container --timestamps
- Instantané d’environnement (sans secrets)
( env | grep -Ev 'KEY|SECRET|TOKEN|PASS' ) | sort > env.snapshot
Vérifications de cause racine (check-list)
Ciblez d’abord les classes d’échecs courantes avant d’explorer des hypothèses rares.
- PATH et résolution de commande
which mycmd || type -a mycmd || echo 'missing'
hash -r
printf '%s\n' "$PATH" | tr ':' '\n'
À interpréter : type -a liste les résolutions et fonctions/alias ; hash -r purge le cache si des binaires ont bougé.
- Shebang et différences de shell
head -1 script.sh # attendre : #!/usr/bin/env bash
bash -n script.sh # vérif de syntaxe
bash script.sh # forcer Bash si /bin/sh -> dash
- Permissions et exécutables
chmod +x script.sh
file script.sh # détecte CRLF
# Si CRLF :
dos2unix script.sh || sed -i 's/\r$//' script.sh
- Guillemets et globbing
echo "$var" # expansions sûres
set -f; printf '%s\n' $user_input; set +f
printf '%s\n' "$user_input" # préférable : toujours citer
- Pipelines et codes de sortie
set -o pipefail
foo | bar | baz; ec=$?; echo "pipeline exit=$ec"
printf '%s\n' "${PIPESTATUS[@]}" # statuts individuels
# Si un non-match de grep est acceptable :
grep -q pattern file || true
- Variables non liées et valeurs par défaut
set -u
name="${NAME:-anonymous}"
api="${API_URL:?API_URL is required}"
- Boucles sûres pour stdin
while IFS= read -r line; do
printf 'line: %s\n' "$line"
done < input.txt
- Fichiers temporaires et concurrence
f=$(mktemp) || exit 1
trap 'rm -f "$f"' EXIT
# utiliser "$f" en toute sécurité ici
Commandes sûres et garde-fous
Préférez les opérations non destructives pendant la récupération et encadrez les effets de bord.
- 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 confirmation
rm -i file # confirmation
- Sauvegardes et points de contrôle
cp -a dir "dir.bak.$(date +%Y%m%d-%H%M%S)"
- Garde-fous locaux (portée réduite)
do_safe() {
set -euo pipefail # attention : modifie la sémantique d’échec
"$@"
}
À interpréter : évitez d’activer set -euo pipefail globalement sans impact analysis ; encapsulez-le dans une fonction/bloc contrôlé.
- Limitation de débit et timeouts
timeout 10s long_running || echo 'timed out'
Mesures de retour arrière (rollback)
Protégez-vous contre les faux positifs et les correctifs risqués.
- Sauvegarde avant modification
cp -a script.sh "script.sh.bak.$(date +%s)"
- Écriture sûre puis bascule atomique
tmp=$(mktemp) && cp script.sh "$tmp" && sed -e 's/old/new/' "$tmp" > "$tmp.edited" \
&& mv -f "$tmp.edited" script.sh
- Revenir en arrière rapidement
mv -f script.sh.bak.* script.sh 2>/dev/null || true
# Dans un dépôt Git
git restore --source=HEAD -- script.sh || git checkout -- script.sh
Récupération pas à pas (playbooks)
Adaptez ces scénarios aux incidents les plus fréquents.
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
PATHdans 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 erreurs 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
printf '%s\n' "${PIPESTATUS[@]}"
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 qui cassent l’exécution
Symptômes : apparition de ^M ou scripts qui échouent dans des conteneurs Linux.
Correctif
file script.sh
dos2unix script.sh || 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 des cibles mal résolues.
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
( env | sort ) > interactive.env
( env -i bash -lc 'env' | sort ) > isolated.env
diff -u interactive.env isolated.env || true
Correctifs
- Définir
PATHexplicitement 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 la 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 (sans les exposer)
ls -l /etc/config /etc/secret
stat /etc/secret/*
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/après wrapper et check-list.
- 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 dépannage Bash s’accélère et devient fiable quand vous suivez un workflow clair, capturez les bons logs, utilisez des commandes de récupération sûres et appliquez des playbooks éprouvés pour les échecs courants. Démarrez petit en local, mesurez l’impact, puis étendez les mêmes schémas à Docker, Docker Compose et Kubernetes (Ingress et Secrets compris). Enrichissez progressivement vos exemples et snippets pour rendre les incidents futurs prévisibles et à faible risque.