infra / linux-bash
Scripting bash avancé (arrays, fonctions, trap, set -euo pipefail)
Explication
Par défaut, bash continue d'exécuter un script même après qu'une commande a échoué : si cd /dossier/inexistant échoue, les commandes suivantes s'exécutent quand même, dans le mauvais dossier. Sur un script lancé une fois à la main, ce n'est pas grave ; sur un script de déploiement automatique, ça peut casser un serveur en production.
Ce que vous allez apprendre
- Comprendre ce que change exactement
set -euo pipefailen tête de script - Garantir un nettoyage systématique avec
trap ... EXIT, même en cas d'erreur - Structurer un script en fonctions réutilisables avec validation d'arguments
- Manipuler des tableaux indexés et des tableaux associatifs (
declare -A) - Utiliser l'expansion de paramètres (
${var#...},${var:-défaut}) pour évitersed/cut
Dans quel contexte ?
Une équipe DevOps écrit un script de déploiement qui doit gérer plusieurs services (api, worker, scheduler) avec des ports différents, tourner sans surveillance humaine via une pipeline CI, et ne jamais laisser de fichier temporaire orphelin sur le disque même s'il échoue en cours de route. Un script bash "simple" écrit sans ces techniques échouerait silencieusement ou laisserait le serveur dans un état incohérent.
Voici comment on corrige ce comportement dès la première ligne
set -euo pipefail change trois choses d'un coup :
| Option | Effet |
|---|---|
-e | Arrête le script à la première commande qui échoue |
-u | Erreur si une variable non définie est utilisée |
-o pipefail | Un pipe échoue si N'IMPORTE QUELLE commande de la chaîne échoue, pas juste la dernière |
C'est devenu le standard de fait en tête de tout script bash sérieux.
Bonne pratique
Commence systématiquement tes scripts de production par #!/usr/bin/env bash suivi de set -euo pipefail. C'est la garantie la plus simple et la plus efficace qu'une erreur ne passera jamais inaperçue en silence.
Une fois cette base posée, il reste un problème : le nettoyage en cas d'échec
Si un script crée un fichier temporaire avec mktemp puis plante avant la fin, ce fichier reste sur le disque indéfiniment. trap cleanup EXIT associe une fonction de nettoyage à la fin du script, qu'il réussisse, échoue, ou soit interrompu par un Ctrl+C — un peu comme un finally dans d'autres langages.
Maintenant que les erreurs sont gérées, structurons le script lui-même
Une fonction comme deployer() avec local environnement="${1:?Usage: deployer <env>}" rend le script lisible et échoue avec un message clair si l'argument attendu manque. Un tableau comme services=("api" "worker" "scheduler") permet de boucler avec for service in "${services[@]}" plutôt que de dupliquer du code trois fois.
declare -A ports puis ports[api]=8000 associe un nom à une valeur, comme un dictionnaire, ce qui remplace avantageusement plusieurs variables séparées (port_api, port_worker...).
Piège fréquent
Oublier les guillemets autour d'une variable dans for service in "${services[@]}" peut casser le script si un élément contient un espace : les guillemets ne sont jamais optionnels avec les tableaux, contrairement à ce qu'on pourrait croire pour une simple boucle.
Maintenant que tu écris des scripts robustes, la prochaine leçon montre comment déboguer ceux qui ne se comportent pas comme prévu.
Commandes & code
Scripting bash avancé
#!/usr/bin/env bash
set -euo pipefail
# -e : arrête le script à la première commande qui échoue
# -u : erreur si une variable non définie est utilisée
# -o pipefail : un pipe échoue si N'IMPORTE QUELLE commande de la chaîne échoue
IFS=$'\n\t' # évite les surprises de word-splitting sur les espaces
# --- Fonctions ---
log() {
local niveau="$1"; shift
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [$niveau] $*"
}
deployer() {
local environnement="${1:?Usage: deployer <env>}" # erreur explicite si $1 manquant
log INFO "Déploiement sur $environnement"
# ...
return 0
}
log INFO "Démarrage du script"
deployer "production"
# --- Tableaux (arrays) ---
services=("api" "worker" "scheduler")
echo "Nombre de services : ${#services[@]}"
echo "Premier service : ${services[0]}"
for service in "${services[@]}"; do
echo "Redémarrage de $service..."
done
# Tableau associatif (bash 4+)
declare -A ports
ports[api]=8000
ports[worker]=8001
for cle in "${!ports[@]}"; do
echo "$cle écoute sur le port ${ports[$cle]}"
done
# --- trap : réagir à un signal ou à la sortie du script (nettoyage garanti) ---
tmpfile=$(mktemp)
cleanup() {
log INFO "Nettoyage..."
rm -f "$tmpfile"
}
trap cleanup EXIT # s'exécute quoi qu'il arrive, même en cas d'erreur
trap 'log WARN "Interrompu par Ctrl+C"; exit 130' INT
# --- Gestion d'erreurs robuste ---
if ! curl -fsS https://api.exemple.com/health > "$tmpfile"; then
log ERROR "Health check échoué"
exit 1
fi
# --- Manipulation de chaînes ---
url="https://api.exemple.com/v1/users"
echo "${url#*://}" # retire le plus court préfixe matchant : api.exemple.com/v1/users
echo "${url##*/}" # retire le plus long préfixe : users
echo "${url%/*}" # retire le plus court suffixe : https://api.exemple.com/v1
nom_fichier="rapport.tar.gz"
echo "${nom_fichier%%.*}" # retire tout après le premier point : rapport
# Valeur par défaut si une variable est vide/non définie
port="${PORT:-8000}"
echo "Utilisation du port $port"
# --- Expansion de paramètres avancée pour valider des entrées ---
: "${DEPLOY_ENV:?Erreur : DEPLOY_ENV doit être défini}"
# --- getopts : parser proprement des options de ligne de commande ---
while getopts "e:v" option; do
case "$option" in
e) environnement="$OPTARG" ;;
v) verbose=true ;;
*) echo "Usage: $0 -e <env> [-v]"; exit 1 ;;
esac
doneRésumé
set -euo pipefaildoit être le premier réflexe de tout script bash sérieux.trap ... EXITgarantit un nettoyage même en cas d'erreur ou d'interruption (Ctrl+C).- Les tableaux associatifs et l'expansion de paramètres (
${var#...},${var:-défaut}) remplacent avantageusementsed/cutpour du texte simple.
Exercices pratiques
Mission : fiabiliser un script de déploiement multi-services
Objectif : Combiner set -euo pipefail, trap et tableaux pour rendre un script de déploiement robuste.
Contexte
Une équipe DevOps te confie un script de déploiement qui gère plusieurs services (api, worker, scheduler) avec des ports différents, destiné à tourner sans surveillance via une pipeline CI. Le script actuel ne commence par aucune protection particulière.