Linux pour les devs - 14 - Scripts bash : automatiser pour ne plus se répéter

Shebang, variables, conditions, boucles, fonctions, exit codes et set -euo pipefail : écrire des scripts bash solides.

  1. 01 Linux pour les devs - 00 - Pourquoi Linux meme si tu codes sur Mac ou Windows
  2. 02 Linux pour les devs - 01 - Le terminal, bash et zsh
  3. 03 Linux pour les devs - 02 - Fichiers et répertoires
  4. 04 Linux pour les devs - 03 - Permissions et droits d'acces
  5. 05 Linux pour les devs - 04 - Utilisateurs, groupes et sudo
  6. 06 Linux pour les devs - 05 - nano, vim, sed et awk
  7. 07 Linux pour les devs - 06 - Pipes et redirections
  8. 08 Linux pour les devs - 07 - grep et find en profondeur
  9. 09 Linux pour les devs - 08 - Les processus : comprendre ce qui tourne sur ta machine
  10. 10 Linux pour les devs - 09 - systemd : gerer tes services comme un pro
  11. 11 Linux pour les devs - 10 - Le réseau : comprendre ce qui passe par le fil
  12. 12 Linux pour les devs - 11 - Le firewall : contrôler qui entre et qui sort
  13. 13 Linux pour les devs - 12 - SSH : l'acces distant sécurisé
  14. 14 Linux pour les devs - 13 - Les variables d'environnement : configurer sans hardcoder
  15. 15 Linux pour les devs - 14 - Scripts bash : automatiser pour ne plus se répéter
  16. 16 Linux pour les devs - 15 - cron : les taches planifiees
  17. 17 Linux pour les devs - 16 - Les logs : lire, filtrer, comprendre
  18. 18 Linux pour les devs - 17 - Stockage et disques
  19. 19 Linux pour les devs - 18 - Les gestionnaires de paquets
  20. 20 Linux pour les devs - 19 - Les conteneurs sans Docker
  21. 21 Linux pour les devs - 20 - Securiser un serveur Linux
  22. 22 Linux pour les devs - 21 - Performance et diagnostic
  23. 23 Linux pour les devs - 22 - tmux : le multiplexeur de terminal
  24. 24 Linux pour les devs - 23 - Glossaire Linux

14 - Scripts bash : automatiser pour ne plus se répéter

Ce que tu vas apprendre

  • Écrire un script bash correct avec le bon shebang
  • Utiliser les variables, le quoting, les conditions et les boucles
  • Gerer les erreurs avec les exit codes et set -euo pipefail
  • Écrire des fonctions reutilisables
  • Un vrai script de déploiement comme exemple concret

Prerequisites

Comprendre les variables d'environnement et etre a l'aise avec le terminal bash.


Pendant longtemps, j'ai écrit mes scripts bash comme on écrit un brouillon : vite, sans structure, sans gestion d'erreurs. Ca marchait jusqu'au jour ou un script de déploiement a continue a s'exécuter apres un échec de build et a ecrase la prod avec du code casse. Depuis, je prends le bash au serieux.

Le shebang : première ligne obligatoire

bash#!/bin/bash
# Ou mieux, pour la portabilite :
#!/usr/bin/env bash

La première forme suppose que bash est dans /bin/bash. La seconde cherche bash dans le PATH, ce qui marche sur plus de systèmes (notamment macOS ou bash peut etre ailleurs).

bash# Rendre le script executable
chmod +x mon-script.sh

# L'executer
./mon-script.sh

Variables et quoting

Le quoting en bash est une source infinie de bugs. La regle est simple : mets toujours tes variables entre guillemets doubles.

bash#!/usr/bin/env bash

# Assigner une variable (pas d'espace autour du =)
nom="Nicolas"
repertoire="/opt/mon app"

# Guillemets doubles : la variable est remplacee par sa valeur
echo "Bonjour $nom"         # Bonjour Nicolas

# Guillemets simples : tout est litteral
echo 'Bonjour $nom'         # Bonjour $nom

# Sans guillemets : DANGER avec les espaces
cd $repertoire   # Erreur ! bash voit: cd /opt/mon app (deux arguments)
cd "$repertoire"  # Correct : cd "/opt/mon app"

# Substitution de commande
date_du_jour=$(date +%Y-%m-%d)
echo "On est le $date_du_jour"

# Variables avec valeur par defaut
port="${PORT:-3000}"  # utilise PORT si defini, sinon 3000

La regle d'or : "$variable" avec des guillemets doubles, toujours. Les seuls cas ou tu peux t'en passer sont les arithmetiques et les comparaisons a l'intérieur de [[ ]].

Conditions

bash#!/usr/bin/env bash

# Syntaxe moderne avec [[ ]] (prefere a [ ])
if [[ -f "/etc/nginx/nginx.conf" ]]; then
    echo "Nginx est installe"
fi

# Tests sur les fichiers
[[ -f chemin ]]   # le fichier existe
[[ -d chemin ]]   # le repertoire existe
[[ -x chemin ]]   # le fichier est executable
[[ -r chemin ]]   # le fichier est lisible

# Tests sur les chaines
[[ -z "$var" ]]   # la chaine est vide
[[ -n "$var" ]]   # la chaine est non vide
[[ "$a" == "$b" ]] # egalite

# Tests numeriques
[[ "$count" -gt 10 ]]  # greater than
[[ "$count" -lt 5 ]]   # less than
[[ "$count" -eq 0 ]]   # equal

# Combinaisons
if [[ -f ".env" ]] && [[ -n "$DB_HOST" ]]; then
    echo "Config OK"
elif [[ -f ".env.example" ]]; then
    echo "Copie .env.example vers .env"
    cp .env.example .env
else
    echo "Pas de fichier de config"
    exit 1
fi

Boucles

bash#!/usr/bin/env bash

# For classique
for fichier in *.log; do
    echo "Traitement de $fichier"
    gzip "$fichier"
done

# For avec une sequence
for i in {1..5}; do
    echo "Iteration $i"
done

# For sur la sortie d'une commande
for serveur in $(cat serveurs.txt); do
    echo "Ping de $serveur"
    ping -c 1 "$serveur"
done

# While : lire un fichier ligne par ligne (la bonne methode)
while IFS= read -r ligne; do
    echo "Ligne: $ligne"
done < fichier.txt

# Until : repeter tant que la condition est fausse
until curl -s http://localhost:3000/health > /dev/null; do
    echo "En attente du serveur..."
    sleep 2
done
echo "Le serveur est pret"

Fonctions

bash#!/usr/bin/env bash

# Definir une fonction
log() {
    echo "[$(date '+%H:%M:%S')] $*"
}

die() {
    echo "ERREUR: $*" >&2
    exit 1
}

check_command() {
    command -v "$1" > /dev/null 2>&1 || die "$1 n'est pas installe"
}

# Utiliser
log "Demarrage du script"
check_command "node"
check_command "rsync"
log "Toutes les dependances sont presentes"

Les fonctions en bash n'ont pas de typage, pas de paramètres nommes. $1, $2, etc. sont les arguments. $* ou $@ represente tous les arguments. C'est rustique mais ca fait le travail.

Exit codes et gestion d'erreurs

Chaque commande retourne un code de sortie. 0 = succes, tout le reste = erreur :

bash# Verifier le code de sortie
grep "pattern" fichier.txt
echo $?  # 0 si trouve, 1 si pas trouve

# Utiliser dans une condition
if grep -q "error" /var/log/app.log; then
    echo "Des erreurs trouvees"
fi

set -euo pipefail : le filet de sécurité

C'est la ligne la plus utile que tu puisses ajouter a tes scripts :

bash#!/usr/bin/env bash
set -euo pipefail

# -e : arrete le script a la premiere erreur
# -u : erreur si une variable n'est pas definie
# -o pipefail : un pipe echoue si n'importe quelle commande du pipe echoue

Sans set -e, un script continue apres une erreur. C'est comme ca que tu te retrouves a déployer du code qui n'a pas compile. Sans set -u, rm -rf "$DOSSIER/" devient rm -rf / si DOSSIER n'est pas défini. Oui, ca arrive.

Exemple réel : script de déploiement

Voici un script que j'utilise sur paltemps.fr (simplifie pour l'article) :

bash#!/usr/bin/env bash
set -euo pipefail

APP_DIR="/opt/paltemps"
BACKUP_DIR="/opt/backups"
BRANCH="main"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}

die() {
    log "ERREUR: $*" >&2
    exit 1
}

# Verifier qu'on est root ou deploy
[[ "$(whoami)" == "deploy" ]] || die "Lance ce script en tant que deploy"

log "Debut du deploiement"

# Backup de la version actuelle
log "Backup de la version actuelle"
timestamp=$(date +%Y%m%d_%H%M%S)
tar -czf "$BACKUP_DIR/backup_$timestamp.tar.gz" -C "$APP_DIR" . \
    || die "Echec du backup"

# Pull des derniers changements
log "Pull depuis $BRANCH"
cd "$APP_DIR"
git fetch origin
git reset --hard "origin/$BRANCH"

# Installation des dependances
log "Installation des dependances"
bun install --frozen-lockfile || die "Echec de bun install"

# Build
log "Build de l'application"
bun run build || die "Echec du build"

# Redemarrage du service
log "Redemarrage du service"
sudo systemctl restart paltemps

# Verification
sleep 3
if curl -sf http://localhost:3000/health > /dev/null; then
    log "Deploiement reussi"
else
    log "Le health check a echoue, rollback"
    tar -xzf "$BACKUP_DIR/backup_$timestamp.tar.gz" -C "$APP_DIR"
    sudo systemctl restart paltemps
    die "Rollback effectue"
fi

# Nettoyage des vieux backups (garder les 5 derniers)
ls -t "$BACKUP_DIR"/backup_*.tar.gz | tail -n +6 | xargs -r rm
log "Termine"

Ce script n'est pas parfait, mais il a les bases : gestion d'erreurs, backup, rollback, vérification. C'est dix fois mieux que git pull && bun run build && systemctl restart app.

Résumé

  • Toujours #!/usr/bin/env bash et set -euo pipefail
  • Guillemets doubles autour de toutes les variables : "$var"
  • [[ ]] pour les conditions (plus sur que [ ])
  • Fonctions log et die dans tous tes scripts
  • Les exit codes sont le mecanisme de communication entre commandes

Article précédent : Les variables d'environnement Article suivant : cron et les taches planifiees

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.