Skip to content

Module 2 — Scripting Bash pour l'automatisation

Bonne lecture et bon apprentissage ! Junior TSAFACK – 12/09/2026 Temps de lecture estimé : 12 minutes


Les scripts de ce module ont été réellement exécutés sur une machine Debian 13, pas rédigés puis simplement décrits. Chaque sortie ci-dessous est une sortie réelle, copiée telle quelle.

Un script de supervision minimal : charge, mémoire, disque, et une alerte simple si un point de montage dépasse 80 % d’utilisation.

health_check.sh
#!/usr/bin/env bash
set -euo pipefail
echo "=== Rapport de santé système — $(date '+%Y-%m-%d %H:%M:%S') ==="
echo
echo "--- Charge système (load average) ---"
uptime
echo
echo "--- Mémoire ---"
free -h
echo
echo "--- Espace disque ---"
df -h --output=target,size,used,avail,pcent | grep -E '^/|Mounted|Avail'
echo
echo "--- Top 5 processus par consommation CPU ---"
ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head -n 6
echo
echo "--- Alerte seuil disque (>80%) ---"
df -h --output=pcent,target | tail -n +2 | while read -r pcent target; do
usage="${pcent%\%}"
if [ "$usage" -ge 80 ]; then
echo "ATTENTION : $target est utilisé à ${usage}%"
fi
done
echo "Vérification terminée."

Exécution réelle :

=== Rapport de santé système — 2026-09-12 22:31:15 ===
--- Charge système (load average) ---
22:31:15 up 0 min, 1 user, load average: 0.00, 0.00, 0.00
--- Mémoire ---
total used free shared buff/cache available
Mem: 7.7Gi 449Mi 7.1Gi 3.4Mi 249Mi 7.2Gi
Swap: 2.0Gi 0B 2.0Gi
--- Top 5 processus par consommation CPU ---
PID COMMAND %CPU %MEM
261 bash 6.6 0.0
1 systemd 3.7 0.1
76 systemd-udevd 2.2 0.1
40 systemd-journal 1.5 0.1
213 systemd 0.8 0.1
--- Alerte seuil disque (>80%) ---
ATTENTION : /usr/lib/wsl/drivers est utilisé à 94%
ATTENTION : /mnt/c est utilisé à 94%
ATTENTION : /mnt/d est utilisé à 96%
Vérification terminée.

Ce genre de script ne remplace pas un vrai outil de supervision (voir le module Observabilité) — mais c’est exactement ce que fait un script de garde (cron toutes les 5 minutes, alerte envoyée si le seuil est dépassé) avant qu’une équipe n’investisse dans Prometheus.

On compresse les journaux de plus de N jours, puis on supprime les archives trop anciennes.

log_cleanup.sh
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="${1:-/var/log/demo-app}"
MAX_AGE_DAYS="${2:-7}"
mkdir -p "$LOG_DIR"
echo "=== Nettoyage des journaux dans $LOG_DIR (plus vieux que $MAX_AGE_DAYS jours) ==="
find "$LOG_DIR" -type f -name '*.log' -mtime "+$MAX_AGE_DAYS" -print0 |
while IFS= read -r -d '' file; do
gzip -f "$file"
echo "Compressé : ${file}.gz"
done
find "$LOG_DIR" -type f -name '*.log.gz' -mtime +30 -print -delete

Pour le tester réellement (pas de simulation), 5 faux journaux ont été créés avec des dates de modification réelles (1 à 5 jours), via touch -d "N days ago" :

--- État avant nettoyage ---
-rw-r--r-- old-1.log (1 jour)
-rw-r--r-- old-2.log (2 jours)
-rw-r--r-- old-3.log (3 jours)
-rw-r--r-- old-4.log (4 jours)
-rw-r--r-- old-5.log (5 jours)
-rw-r--r-- recent.log (1 heure)
--- Fichiers ciblés (mtime > 3 jours) ---
/tmp/demo-app/logs/old-5.log
/tmp/demo-app/logs/old-4.log
--- Compression puis suppression des originaux ---
Compressé : /tmp/demo-app/logs/old-5.log.gz
Compressé : /tmp/demo-app/logs/old-4.log.gz
--- État après nettoyage ---
old-1.log, old-2.log, old-3.log, recent.log (inchangés)
old-4.log.gz, old-5.log.gz (nouvellement compressés)

Avec un seuil de 3 jours, seuls old-4.log et old-5.log dépassent l’âge limite — exactement les deux fichiers compressés. Le comportement est vérifié, pas supposé.

Un script de sauvegarde qui archive un dossier, puis ne conserve que les N sauvegardes les plus récentes.

backup_rotate.sh
#!/usr/bin/env bash
set -euo pipefail
SRC_DIR="${1:-/opt/demo-app/data}"
BACKUP_DIR="${2:-/opt/backups}"
KEEP="${3:-3}"
mkdir -p "$SRC_DIR" "$BACKUP_DIR"
timestamp="$(date '+%Y%m%d-%H%M%S')"
archive="$BACKUP_DIR/backup-${timestamp}.tar.gz"
tar -czf "$archive" -C "$(dirname "$SRC_DIR")" "$(basename "$SRC_DIR")"
echo "Archive créée : $archive"
mapfile -t backups < <(ls -1t "$BACKUP_DIR"/backup-*.tar.gz)
if [ "${#backups[@]}" -gt "$KEEP" ]; then
for old in "${backups[@]:$KEEP}"; do
echo "Suppression (trop ancienne) : $old"
rm -f "$old"
done
fi

Exécuté 4 fois de suite avec KEEP=3 :

Backup 1 → 1 sauvegarde présente, rien à supprimer
Backup 2 → 2 sauvegardes présentes, rien à supprimer
Backup 3 → 3 sauvegardes présentes, rien à supprimer
Backup 4 → 4 sauvegardes présentes
Suppression (trop ancienne) : backup-20260912-223208.tar.gz
→ 3 sauvegardes conservées (les plus récentes)

La rotation ne se déclenche exactement qu’à la 4ᵉ exécution, quand le nombre de sauvegardes dépasse la limite — comportement attendu, vérifié en conditions réelles plutôt que déduit du code.

user_audit.sh
#!/usr/bin/env bash
set -euo pipefail
echo "--- Comptes humains (UID >= 1000) ---"
awk -F: '$3 >= 1000 && $3 < 65534 {printf "%-15s UID=%-6s shell=%s\n", $1, $3, $7}' /etc/passwd
echo "--- Membres du groupe sudo ---"
getent group sudo | awk -F: '{print $4}'
echo "--- Comptes avec mot de passe vide ---"
while IFS=: read -r user pass _; do
[ -z "$pass" ] && echo "ALERTE : $user n'a pas de mot de passe"
done < /etc/shadow

Lire /etc/shadow nécessite les droits root — exécuté avec les privilèges administrateur, sur la vraie machine :

--- Comptes humains (UID >= 1000) ---
junior UID=1000 shell=/bin/bash
batman UID=1001 shell=/bin/sh
--- Membres du groupe sudo ---
junior
--- Comptes avec mot de passe vide ou verrouillé ---
(aucune alerte : tous les comptes ont un mot de passe défini)
open_ports_check.sh
#!/usr/bin/env bash
set -euo pipefail
ALLOWED_PORTS=(22 80 443)
listening_ports="$(ss -tln | awk 'NR>1 {print $4}' | grep -oE '[0-9]+$' | sort -un)"
for port in $listening_ports; do
allowed=0
for a in "${ALLOWED_PORTS[@]}"; do
[ "$port" -eq "$a" ] && allowed=1 && break
done
if [ "$allowed" -eq 1 ]; then
echo "OK : port $port (attendu)"
else
echo "ALERTE : port $port en écoute mais absent de la liste blanche"
fi
done

Sortie réelle sur la machine de test :

--- Ports TCP en LISTEN ---
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 1000 10.255.255.254:53 0.0.0.0:*
--- Comparaison avec la liste blanche (22 80 443) ---
ALERTE : port 53 en écoute mais absent de la liste blanche

Le port 53 détecté est un vrai résolveur DNS local propre à l’environnement WSL de la machine de test — ce n’est pas une faille, mais le script fait exactement ce qu’on attend : il signale tout ce qui n’est pas dans la liste blanche, à charge pour l’humain de qualifier l’alerte (c’est le principe même d’un contrôle de conformité : il détecte des écarts, il ne juge pas leur gravité à votre place).

Module 3 : Bonnes pratiques et pièges réels du scripting shell — où l’on montre un vrai bug rencontré en écrivant les scripts de ce module, pas un piège théorique.