Les scripts Bash et shell alimentent encore les jobs CI, les serveurs et les correctifs rapides. Découvrez où ils excellent, comment écrire des scripts plus sûrs et quand utiliser d'autres outils.

Quand on parle de « script shell », on entend généralement un petit programme qui s'exécute dans un shell en ligne de commande. Le shell lit vos commandes et lance d'autres programmes. Sur la plupart des serveurs Linux, ce shell est soit POSIX sh (une base standardisée), soit Bash (le shell « sh-like » le plus répandu avec des fonctionnalités supplémentaires).
En termes DevOps, les scripts shell sont la fine couche de glue qui relie les outils OS, les CLIs cloud, les outils de build et les fichiers de configuration.
Les machines Linux livrent déjà des utilitaires de base (comme grep, sed, awk, tar, curl, systemctl). Un script shell peut appeler ces outils directement sans ajouter de runtime, de paquets ou de dépendances supplémentaires — particulièrement utile dans des images minimales, des shells de récupération ou des environnements verrouillés.
Le scripting shell brille parce que la plupart des outils respectent des contrats simples :
cmd1 | cmd2).0 signifie succès ; non-zéro signifie échec — critique pour l'automatisation.Nous nous concentrerons sur la place de Bash/shell dans l'automatisation DevOps, les CI/CD, les conteneurs, le dépannage, la portabilité et les pratiques de sécurité. Nous n'essayerons pas de transformer le shell en un framework applicatif — quand c'est nécessaire, nous indiquerons d'autres options (et comment le shell peut encore jouer un rôle autour d'elles).
Le scripting shell n'est pas seulement du « legacy glue ». C'est une couche légère et fiable qui transforme des suites de commandes manuelles en actions reproductibles — surtout quand vous vous déplacez rapidement entre serveurs, environnements et outils.
Même si l'objectif final est une infrastructure entièrement managée, il y a souvent un moment où il faut préparer un hôte : installer un paquet, déposer un fichier de config, définir des permissions, créer un utilisateur ou récupérer des secrets depuis une source sûre. Un court script shell est parfait pour ces tâches ponctuelles (ou rarement répétées) car il s'exécute partout où il y a un shell et SSH.
Beaucoup d'équipes gardent des runbooks sous forme de documents, mais les runbooks les plus utiles sont des scripts exécutables :
Transformer un runbook en script réduit les erreurs humaines, rend les résultats plus cohérents et améliore les passations.
Quand un incident survient, vous ne voulez généralement pas toute une application ou un dashboard — vous voulez de la clarté. Les pipelines shell avec grep, sed, awk et jq restent le moyen le plus rapide pour tailler des logs, comparer des sorties et repérer des motifs sur plusieurs nœuds.
Le travail quotidien implique souvent d'exécuter les mêmes étapes CLI en dev, staging et prod : taguer des artefacts, synchroniser des fichiers, vérifier des statuts ou réaliser des rollouts sécurisés. Les scripts shell capturent ces workflows pour qu'ils soient cohérents entre les environnements.
Tout ne s'intègre pas proprement. Les scripts shell peuvent relier « l'outil A sort du JSON » à « l'outil B attend des variables d'environnement », orchestrer des appels et ajouter des vérifications/retries manquants — sans attendre de nouvelles intégrations ou plugins.
Les scripts shell et des outils comme Terraform, Ansible, Chef et Puppet résolvent des problèmes voisins, mais ne sont pas interchangeables.
Pensez à l'IaC/gestion de config comme au système de référence : l'endroit où l'état désiré est défini, revu, versionné et appliqué de manière cohérente. Terraform déclare l'infrastructure (réseaux, load balancers, bases de données). Ansible/Chef/Puppet décrivent la configuration machine et la convergence continue.
Les scripts shell sont généralement du glue code : la fine couche qui connecte étapes, outils et environnements. Un script peut ne pas « posséder » l'état final, mais il rend l'automatisation pratique en coordonnant les actions.
Le shell est un excellent compagnon de l'IaC quand vous avez besoin de :
Exemple : Terraform crée des ressources, mais un script Bash valide les entrées, s'assure que le backend correct est configuré et lance terraform plan + contrôles de politique avant d'autoriser apply.
Le shell est rapide à implémenter et a des dépendances minimales — idéal pour l'automatisation urgente et les petites tâches de coordination. L'inconvénient est la gouvernance à long terme : les scripts peuvent dériver en « mini-plateformes » avec des patterns inconsistants, une idempotence faible et un audit limité.
Règle pratique : utilisez des outils IaC/gestion de config pour l'infrastructure et la configuration étatuelle et répétable ; utilisez le shell pour des workflows courts et composables autour d'eux. Quand un script devient critique pour le business, migrez la logique centrale dans l'outil système-de-référence et gardez le shell comme wrapper.
Les systèmes CI/CD orchestrent des étapes, mais ils ont toujours besoin de quelque chose pour faire le travail. Bash (ou POSIX sh) reste le glue par défaut car il est disponible sur la plupart des runners, facile à invoquer et peut chaîner des outils sans dépendances runtime supplémentaires.
La plupart des pipelines utilisent des étapes shell pour les tâches peu glamour mais essentielles : installer des dépendances, exécuter des builds, empaqueter les sorties et uploader des artefacts.
Exemples types :
Les pipelines transmettent la configuration via des variables d'environnement, donc les scripts shell deviennent naturellement le routeur pour ces valeurs. Un pattern sûr : lire les secrets depuis l'env, ne jamais les echo, et éviter de les écrire sur disque.
Préférez :
set +x autour des sections sensibles (pour que les commandes ne soient pas imprimées)Le CI attend un comportement prévisible. Bons scripts de pipeline :
Le caching et les étapes parallèles sont généralement contrôlés par le système CI, pas par le script — Bash ne peut pas gérer de manière fiable des caches partagés entre jobs. Ce qu'il peut faire, c'est rendre les clés de cache et les répertoires cohérents.
Pour garder les scripts lisibles entre équipes, traitez-les comme du code produit : petites fonctions, nommage cohérent et un en-tête d'usage court. Stockez les scripts partagés en-repo (par exemple sous /ci/) pour que les changements soient revus avec le code qu'ils construisent.
Si votre équipe écrit constamment « encore un script CI », un workflow assisté par IA peut aider — surtout pour le boilerplate comme le parsing d'arguments, les retries, le logging sûr et les garde-fous. Sur Koder.ai, vous pouvez décrire le job de pipeline en langage naturel et générer un script Bash/sh de départ, puis itérer en mode planification avant exécution. Comme Koder.ai supporte l'export du code source ainsi que snapshots et rollback, il est plus simple de traiter les scripts comme des artefacts revus plutôt que comme des snippets ad hoc copiés dans un YAML CI.
Le scripting shell reste une couche de glue pratique dans les workflows conteneurs et cloud car de nombreux outils exposent d'abord une CLI. Même lorsque votre infrastructure est définie ailleurs, il faut des petites automatisations fiables pour lancer, valider, collecter et récupérer.
Un endroit courant pour voir du shell est l'entrypoint du conteneur. De petits scripts peuvent :
L'important est de garder les entrypoints courts et prévisibles : faire la configuration puis exec le processus principal pour que les signaux et codes de sortie soient corrects.
Le travail quotidien Kubernetes bénéficie souvent d'helpers légers : wrappers kubectl qui confirment que vous êtes sur le bon contexte/espace de noms, collectent les logs de plusieurs pods ou récupèrent les événements récents lors d'un incident.
Par exemple, un script peut refuser de s'exécuter si vous êtes pointé vers production, ou agréger automatiquement des logs en un seul artefact pour un ticket.
Les CLIs AWS/Azure/GCP sont idéales pour des tâches en batch : taguer des ressources, faire tourner des secrets, exporter des inventaires ou arrêter des environnements non-prod la nuit. Le shell est souvent le moyen le plus rapide d'enchaîner ces actions en commande reproductible.
Deux points de défaillance courants sont le parsing fragile et les APIs peu fiables. Préférez les sorties structurées quand c'est possible :
--output json) et parsez avec jq plutôt que de greper des tableaux formatés pour humain.Un petit changement — JSON + jq, plus une logique de retry basique — transforme des scripts « ça marche sur mon laptop » en automatisation fiable qu'on peut relancer.
Quand quelque chose casse, vous n'avez généralement pas besoin d'une nouvelle chaîne d'outils — vous avez besoin de réponses en minutes. Le shell est parfait pour la réponse aux incidents car il est déjà sur l'hôte, il s'exécute vite et peut agencer de petites commandes fiables pour dresser un tableau clair de la situation.
Pendant une panne, vous validez souvent quelques basiques :
df -h, df -i)free -m, vmstat 1 5, uptime)ss -lntp, ps aux | grep ...)getent hosts name, dig +short name)curl -fsS -m 2 -w '%{http_code} %{time_total}\n' URL)Les scripts shell brillent ici parce que vous pouvez standardiser ces vérifications, les exécuter de façon cohérente sur plusieurs hôtes et coller les résultats dans le canal d'incident sans mise en forme manuelle.
Un bon script d'incident collecte un snapshot : horodatages, nom d'hôte, version du kernel, logs récents, connexions en cours et usage des ressources. Ce « bundle d'état » aide l'analyse post-mortem une fois l'incendie éteint.
#!/usr/bin/env bash
set -euo pipefail
out="incident_$(hostname)_$(date -u +%Y%m%dT%H%M%SZ).log"
{
date -u
hostname
uname -a
df -h
free -m
ss -lntp
journalctl -n 200 --no-pager 2>/dev/null || true
} | tee "$out"
L'automatisation d'incident doit être en lecture seule d'abord. Traitez les actions de « correction » comme explicites, avec des invites de confirmation (ou un flag --yes) et une sortie claire sur ce qui va changer. Ainsi, le script aide les intervenants à aller plus vite — sans créer un second incident.
La portabilité compte quand votre automatisation s'exécute sur « ce que le runner a sous la main » : conteneurs minimaux (Alpine/BusyBox), distributions Linux différentes, images CI ou postes développeurs (macOS). La plus grande source de douleur est de supposer que chaque machine a le même shell.
POSIX sh est le dénominateur commun le plus bas : variables basiques, case, for, if, pipelines et fonctions simples. C'est le choix quand vous voulez que le script fonctionne presque partout.
Bash est un shell riche en fonctionnalités avec des commodités comme les tableaux, [[ ... ]], la substitution de processus (<(...)), set -o pipefail, le globbing étendu et une meilleure manipulation des chaînes. Ces fonctionnalités accélèrent l'automatisation DevOps — mais elles peuvent casser sur des systèmes où /bin/sh n'est pas Bash.
sh pour une portabilité maximale (Alpine ash, Debian dash, BusyBox).Sur macOS, les utilisateurs peuvent avoir Bash 3.2 par défaut, tandis que les images Linux CI ont Bash 5.x — ainsi même un script « Bash » peut rencontrer des différences de version.
Des bashisms courants : [[ ... ]], les tableaux, source (utilisez .), et le comportement variable de echo -e. Si vous visez POSIX, écrivez et testez avec un vrai shell POSIX (ex. dash ou BusyBox sh).
Utilisez un shebang qui reflète votre intention :
#!/bin/sh
ou :
#!/usr/bin/env bash
Puis documentez les exigences dans le repo (par ex. « requiert Bash ≥ 4.0 ») pour que CI, conteneurs et coéquipiers restent alignés.
Exécutez shellcheck en CI pour signaler les bashisms, les erreurs de quoting et les patterns dangereux. C'est l'une des façons les plus rapides d'éviter les pannes “ça marche sur ma machine” pour les shells. Pour des idées d'intégration, renvoyez votre équipe à un guide interne simple comme /blog/shellcheck-in-ci.
Les scripts shell s'exécutent souvent avec accès aux systèmes production, aux credentials et aux logs sensibles. Quelques habitudes défensives font la différence entre une « automation utile » et un incident.
Beaucoup d'équipes commencent leurs scripts par :
set -euo pipefail
-e arrête sur erreur, mais peut surprendre dans des conditions if, while et certains pipelines. Connaissez les endroits où des échecs sont attendus et gérez-les explicitement.-u traite les variables non définies comme des erreurs — idéal pour attraper des fautes de frappe.pipefail garantit qu'une commande échouée dans un pipeline fait échouer le pipeline entier.Quand vous autorisez volontairement une commande à échouer, rendez-le évident : command || true, ou mieux, vérifiez et gérez l'erreur.
Les variables non citées peuvent causer du word-splitting et de l'expansion de glob :
rm -rf $TARGET # dangereux
rm -rf -- "$TARGET" # plus sûr
Quotez toujours les variables à moins que vous ne souhaitiez explicitement le splitting. Préférez les tableaux en Bash pour construire des arguments de commande.
eval, utilisez le moindre privilègeTraitez les paramètres, variables d'env, noms de fichiers et sorties de commandes comme non fiables.
eval et la construction de code shell sous forme de chaînes.sudo pour une commande unique, pas pour tout le script.echo, traces debug, sorties curl verbeuses).set -x ; désactivez le traçage autour des commandes sensibles.Utilisez mktemp pour les fichiers temporaires et trap pour le nettoyage :
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
Utilisez aussi -- pour terminer le parsing des options (rm -- "$file") et définissez un umask restrictif lorsque vous créez des fichiers pouvant contenir des données sensibles.
Les scripts shell commencent souvent comme une réparation rapide, puis deviennent silencieusement « production ». La maintenabilité empêche que cela devienne un fichier mystérieux que personne n'ose toucher.
Un peu de structure paye vite :
scripts/ (ou ops/) pour qu'ils soient trouvables.backup-db.sh, rotate-logs.sh, release-tag.sh) plutôt que des noms internes amusants.Dans le script, préférez des fonctions lisibles (petites, à responsabilité unique) et un logging cohérent. Un simple pattern log_info / log_warn / log_error accélère le dépannage et évite le spam d'echo incohérent.
Supportez aussi -h/--help. Même un message d'usage minimal transforme un script en un outil que vos collègues peuvent exécuter en confiance.
Le shell n'est pas difficile à tester — il est juste facile d'oublier. Commencez léger :
--dry-run) et valident la sortie.Concentrez les tests sur entrées/sorties : arguments, statut de sortie, lignes de log et effets de bord (fichiers créés, commandes invoquées).
Deux outils attrapent la plupart des problèmes avant revue :
Exécutez les deux en CI pour que les standards ne dépendent pas de qui se souvient de lancer quoi.
Les scripts opérationnels devraient être versionnés, revus et liés à la gestion des changements comme le code applicatif. Exigez des PRs pour les modifications, documentez les changements de comportement dans les messages de commit et envisagez des tags de version simples quand les scripts sont consommés par plusieurs repos ou équipes.
Les scripts fiables se comportent comme une bonne automation : prévisibles, sûrs à relancer et lisibles sous pression. Quelques patterns transforment « ça marche sur ma machine » en quelque chose que l'équipe peut faire confiance à long terme.
Supposez que le script sera exécuté deux fois — par des humains, cron ou un job CI qui retry. Préférez « assurer un état » plutôt que « faire une action ».
mkdir -p, pas mkdir.Règle simple : si l'état désiré existe déjà, le script doit réussir sans travaux supplémentaires.
Les réseaux échouent. Les registries limitent. Les APIs timeoutent. Entourez les opérations fragiles de retries et délais croissants.
retry() {
n=0; max=5; delay=1
while :; do
"$@" && break
n=$((n+1))
[ "$n" -ge "$max" ] && return 1
sleep "$delay"; delay=$((delay*2))
done
}
Pour l'automatisation, considérez le statut HTTP comme des données. Préférez curl -fsS (échoue sur non-2xx, affiche les erreurs) et capturez le statut quand nécessaire.
resp=$(curl -sS -w "\n%{http_code}" -H "Authorization: Bearer $TOKEN" "$URL")
body=${resp%$'\n'*}; code=${resp##*$'\n'}
[ "$code" = "200" ] || { echo "API failed: $code" >&2; exit 1; }
Si vous devez parser du JSON, utilisez jq plutôt que des pipelines fragiles basés sur grep.
Deux copies d'un script se battant pour la même ressource est un pattern d'incident courant. Utilisez flock quand disponible, ou un lockfile avec vérification du PID.
Loggez clairement (timestamps, actions clés), mais proposez aussi un mode lisible par machine (JSON) pour les dashboards et artefacts CI. Un petit flag --json paie souvent dès la première fois où on a besoin d'automatiser du reporting.
Le shell est excellent pour la glue : enchaîner des commandes, déplacer des fichiers et coordonner des outils déjà présents. Mais ce n'est pas le meilleur choix pour tous les types d'automatisation.
Passez au-delà du shell quand le script commence à ressembler à une petite application :
if imbriqués, flags temporaires, cas spéciaux)Python excelle pour l'intégration d'APIs (fournisseurs cloud, systèmes de ticketing), la manipulation JSON/YAML et quand on a besoin de tests unitaires et de modules réutilisables. Si votre « script » requiert un vrai handling d'erreurs, du logging riche et une configuration structurée, Python réduit souvent la fragilité du parsing.
Go est un bon choix pour des outils distribuables : binaire statique unique, performances prévisibles et typage fort qui attrape des erreurs tôt. Idéal pour des CLI internes que vous voulez exécuter dans des conteneurs minimaux ou des hôtes verrouillés sans runtime complet.
Un pattern pratique est d'utiliser le shell comme wrapper pour un vrai outil :
C'est aussi là où des plateformes comme Koder.ai trouvent leur place : prototypez le workflow en tant que wrapper Bash mince, puis générez ou scaffoldez l'outil plus lourd (service backend, etc.). Quand la logique « monte en grade » de script ops à produit interne, exportez le code et migrez-le dans votre repo/CI pour garder la gouvernance.
Choisissez shell si c'est surtout : orchestrer des commandes, de courte durée et facile à tester dans un terminal.
Choisissez un autre langage si vous avez besoin de : bibliothèques, données structurées, support cross-plateforme ou code maintenable avec tests qui va croître dans le temps.
Apprendre Bash pour le DevOps fonctionne mieux si vous le traitez comme une boîte à outils, pas comme un langage à maîtriser entièrement d'un coup. Concentrez-vous sur les 20% que vous utiliserez chaque semaine, puis ajoutez des fonctionnalités seulement quand la douleur se fait sentir.
Commencez par les commandes et règles qui rendent l'automatisation prévisible :
ls, find, grep, sed, awk, tar, curl, jq (oui, ce n'est pas le shell — mais c'est essentiel)|, >, >>, 2>, 2>&1, here-strings$?, compromis set -e, et contrôles explicites comme cmd || exit 1"$var", tableaux et quand le word-splitting pose problèmefoo() { ... }, $1, $@, valeurs par défautVisez à écrire de petits scripts qui collent des outils ensemble plutôt que de grosses « applications ».
Choisissez un projet court par semaine et gardez-le exécutable depuis un terminal frais :
Gardez chaque script d'abord sous ~100 lignes. S'il grossit, fractionnez en fonctions.
Utilisez les sources primaires plutôt que des snippets aléatoires :
man bash, help set et man testCréez un template de démarrage simple et une checklist de revue :
set -euo pipefail (ou une alternative documentée)trap pour cleanupLe scripting shell rapporte le plus quand vous avez besoin d'une glue rapide et portable : exécuter des builds, inspecter des systèmes et automatiser des tâches d'administration répétables avec des dépendances minimales.
Si vous standardisez quelques valeurs par défaut de sécurité (quotage, validation d'entrée, retries, linting), le shell devient une partie fiable de votre stack d'automatisation — pas un ensemble de rustines fragiles. Et quand vous voulez transformer un « script » en « produit », des outils comme Koder.ai peuvent vous aider à faire évoluer cette automation vers une appli maintenable ou un outil interne tout en conservant le contrôle sur le code, les revues et les rollbacks.
En DevOps, un script shell est généralement du glue code : un petit programme qui enchaîne des outils existants (utilitaires Linux, CLIs cloud, étapes CI) en s'appuyant sur des pipes, des codes de sortie et des variables d'environnement.
C'est idéal quand vous avez besoin d'une automatisation rapide et légère en dépendances sur des serveurs ou des runners où le shell est déjà disponible.
Utilisez POSIX sh quand le script doit s'exécuter dans des environnements variés (BusyBox/Alpine, conteneurs minimaux, runners CI inconnus).
Utilisez Bash quand vous contrôlez le runtime (l'image CI, une machine d'exploitation) ou si vous avez besoin de fonctionnalités Bash comme [[ ... ]], les tableaux, pipefail ou la substitution de processus.
Fixez l'interpréteur via le shebang (par exemple #!/bin/sh ou #!/usr/bin/env bash) et documentez les versions requises.
Parce qu'il est déjà là : la plupart des images Linux incluent un shell et des utilitaires de base (grep, sed, awk, tar, curl, systemctl).
Cela rend le shell idéal pour :
Les outils IaC/de configuration sont généralement le système de référence (etat désiré, changements révisables, applications répétables). Les scripts shell sont mieux utilisés comme wrapper qui ajoute orchestration et garde-fous.
Exemples où le shell complète l'IaC :
plan/applyRendez-les prévisibles et sûrs :
set +x autour des commandes sensiblesjq plutôt que de greper des tablesSi une étape est instable (réseau/API), ajoutez des retries avec backoff et un échec net quand le maximum est atteint.
Gardez les entrypoints petits et déterministes :
exec le processus principal pour que les signaux et codes de sortie se propagent correctementEvitez les processus de longue durée en arrière-plan dans l'entrypoint à moins d'avoir une stratégie de supervision claire ; sinon les arrêts et redémarrages deviennent imprévisibles.
Pièges courants :
/bin/sh peut être dash (Debian/Ubuntu) ou BusyBox sh (Alpine), pas Bashecho -e, et la syntaxe des tests varient selon les plateformesUne base solide :
set -euo pipefail
Puis adoptez ces habitudes :
Pour des diagnostics rapides et cohérents, standardisez un petit ensemble de commandes et capturez les sorties avec des timestamps.
Vérifications typiques :
Deux outils couvrent la plupart des besoins :
Ajoutez des tests légers :
sed -iSi la portabilité importe, testez avec le shell cible (par exemple dash/BusyBox) et exécutez ShellCheck en CI pour repérer les « bashisms » tôt.
"$var" (évite les erreurs de découpage/globbing)eval et la construction de commandes comme chaînes-- pour terminer le parsing des options (ex. rm -- "$file")mktemp + trap pour des fichiers temporaires sécurisés et le cleanupFaites attention à set -e : gérez explicitement les échecs attendus (cmd || true ou contrôles appropriés).
df -h, df -iuptime, free -m, vmstat 1 5ss -lntpjournalctl -n 200 --no-pagercurl -fsS -m 2 URLPrivilégiez d'abord des scripts en lecture seule, et faites toute action de correction explicite (prompt ou --yes).
--dry-run)bats si vous voulez des assertions sur codes de sortie, sorties et changements de fichiersRangez les scripts dans un emplacement prévisible (ex. scripts/ ou ops/) et incluez un bloc --help minimal.