Exercice 09 : Scripts Bash : variables, conditions et boucles
🎯 Objectifs
À la fin de cet exercice, vous serez capable de :
- ✅ Démarrer chaque script avec
set -euo pipefail(fail-safe) - ✅ Utiliser les variables positionnelles
$1,$2,$#,$@ - ✅ Écrire des conditions
if/elif/elseavec tests de fichiers et de chaînes - ✅ Utiliser
casepour les alternatives multiples - ✅ Itérer avec
for,whileetuntil
Durée estimée : 35 minutes
Difficulté : ⭐⭐⭐☆☆ (Avancé)
Prérequis : Maîtrise du terminal Linux, avoir complété les modules débutant et intermédiaire
📖 Contexte
Un script Bash sans structure est fragile : une commande qui échoue silencieusement, une variable non définie qui prend une valeur vide, et c'est tout le script qui part en vrille. Les trois lignes magiques set -euo pipefail transforment un script fragile en script qui échoue vite et clairement.
📋 Énoncé
Écrivez une suite de scripts Bash de complexité croissante pour automatiser des tâches d'administration système.
🧭 Déroulement de l'exercice
Tâche 1 : Le script fail-safe de base
Créez 01-structure.sh qui montre la structure minimale d'un script robuste :
cat > 01-structure.sh << 'EOF'
#!/usr/bin/env bash
# Description : Template de script Bash robuste
# Usage : ./01-structure.sh <nom_utilisateur>
# FONDAMENTAL : ces 3 options rendent le script fail-safe
set -e # Exit immédiatement si une commande échoue
set -u # Erreur si une variable non définie est utilisée
set -o pipefail # Un pipe échoue si n'importe quelle commande du pipe échoue
# Raccourci : set -euo pipefail
# Variables avec valeurs par défaut
NOM_SCRIPT="$(basename "$0")"
UTILISATEUR="${1:-}" # Vide si pas d'argument
# Validation des arguments
if [[ -z "$UTILISATEUR" ]]; then
echo "Usage: $NOM_SCRIPT <nom_utilisateur>" >&2
exit 1
fi
if [[ "$UTILISATEUR" =~ [^a-zA-Z0-9_-] ]]; then
echo "Erreur: nom d'utilisateur invalide '$UTILISATEUR'" >&2
exit 2
fi
echo "Bonjour, $UTILISATEUR !"
echo "Date : $(date '+%Y-%m-%d %H:%M:%S')"
echo "Répertoire courant : $PWD"
EOF
chmod +x 01-structure.shTest :
./01-structure.sh # Doit afficher le message d'usage et exit 1
./01-structure.sh alice # Doit afficher "Bonjour, alice !"
./01-structure.sh "bad name" # Doit rejeter le nom invalide
echo "Code de sortie : $?"Vérification : Sans argument, le script retourne le code 1 et affiche le message d'usage sur stderr.
Tâche 2 : Conditions et tests de fichiers
cat > 02-conditions.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
CIBLE="${1:-}"
if [[ -z "$CIBLE" ]]; then
echo "Usage: $0 <chemin>" >&2
exit 1
fi
# Tests de fichiers
if [[ -f "$CIBLE" ]]; then
TAILLE=$(du -h "$CIBLE" | cut -f1)
echo "Fichier normal : $CIBLE (taille: $TAILLE)"
# Droits
if [[ -r "$CIBLE" ]]; then echo " → Lisible"; fi
if [[ -w "$CIBLE" ]]; then echo " → Modifiable"; fi
if [[ -x "$CIBLE" ]]; then echo " → Exécutable"; fi
elif [[ -d "$CIBLE" ]]; then
NB_FICHIERS=$(find "$CIBLE" -maxdepth 1 -type f | wc -l)
echo "Répertoire : $CIBLE ($NB_FICHIERS fichiers directs)"
elif [[ -L "$CIBLE" ]]; then
CIBLE_LIEN=$(readlink -f "$CIBLE")
echo "Lien symbolique : $CIBLE → $CIBLE_LIEN"
else
echo "Chemin inexistant ou type inconnu : $CIBLE"
exit 3
fi
# Case pour l'extension
if [[ -f "$CIBLE" ]]; then
EXTENSION="${CIBLE##*.}"
case "$EXTENSION" in
sh|bash) echo " Type: script shell" ;;
py) echo " Type: script Python" ;;
json|yaml) echo " Type: fichier de configuration" ;;
log) echo " Type: fichier de log" ;;
*) echo " Type: extension inconnue (.$EXTENSION)" ;;
esac
fi
EOF
chmod +x 02-conditions.sh
./02-conditions.sh /etc/hosts
./02-conditions.sh /tmp
./02-conditions.sh 01-structure.shTâche 3 : Boucles for et while
cat > 03-boucles.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
echo "=== Boucle for sur des valeurs ==="
SERVICES=("nginx" "ssh" "cron" "docker")
for SERVICE in "${SERVICES[@]}"; do
# Vérifier si le service est actif (sans planter si absent)
if systemctl is-active --quiet "$SERVICE" 2>/dev/null; then
echo " ✅ $SERVICE : actif"
else
echo " ❌ $SERVICE : inactif ou absent"
fi
done
echo ""
echo "=== Boucle for sur des fichiers ==="
COMPTEUR=0
for FICHIER in /etc/*.conf; do
[[ -f "$FICHIER" ]] || continue # Ignorer si pas un fichier normal
COMPTEUR=$((COMPTEUR + 1))
done
echo "Fichiers .conf dans /etc : $COMPTEUR"
echo ""
echo "=== Boucle while avec compteur ==="
TENTATIVE=1
MAX_TENTATIVES=3
while [[ $TENTATIVE -le $MAX_TENTATIVES ]]; do
echo " Tentative $TENTATIVE/$MAX_TENTATIVES..."
# Simuler une opération (succès à la tentative 2)
if [[ $TENTATIVE -eq 2 ]]; then
echo " ✅ Succès à la tentative $TENTATIVE"
break
fi
TENTATIVE=$((TENTATIVE + 1))
sleep 0.1 # Pause courte entre les tentatives
done
if [[ $TENTATIVE -gt $MAX_TENTATIVES ]]; then
echo " ❌ Echec après $MAX_TENTATIVES tentatives"
exit 1
fi
EOF
chmod +x 03-boucles.sh
./03-boucles.shVérification : Le script affiche l'état des services, le nombre de .conf, et simule une boucle de retry.
Tâche 4 : Script de surveillance des disques avec alertes
Assemblez les acquis dans un script utile :
cat > 04-surveiller-disques.sh << 'EOF'
#!/usr/bin/env bash
# Surveille l'utilisation des disques et alerte si seuil dépassé
set -euo pipefail
SEUIL_ALERTE="${1:-80}" # Seuil par défaut : 80%
LOG_FILE="${2:-/tmp/disk-alert.log}"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
ALERTES=0
# Valider que le seuil est un nombre entre 1 et 99
if ! [[ "$SEUIL_ALERTE" =~ ^[0-9]+$ ]] || [[ "$SEUIL_ALERTE" -lt 1 || "$SEUIL_ALERTE" -gt 99 ]]; then
echo "Erreur: le seuil doit être un nombre entre 1 et 99" >&2
exit 1
fi
echo "[$DATE] Vérification des disques (seuil: ${SEUIL_ALERTE}%)" | tee -a "$LOG_FILE"
echo "---" | tee -a "$LOG_FILE"
# Lire la sortie de df ligne par ligne
while IFS= read -r LIGNE; do
# Extraire le pourcentage et le point de montage
POURCENTAGE=$(echo "$LIGNE" | awk '{print $5}' | tr -d '%')
MONTAGE=$(echo "$LIGNE" | awk '{print $6}')
# Ignorer les en-têtes et les systèmes de fichiers virtuels
[[ "$POURCENTAGE" =~ ^[0-9]+$ ]] || continue
[[ "$MONTAGE" =~ ^/(dev|proc|sys|run) ]] && continue
if [[ "$POURCENTAGE" -ge "$SEUIL_ALERTE" ]]; then
MSG="⚠️ ALERTE: $MONTAGE à ${POURCENTAGE}% (seuil: ${SEUIL_ALERTE}%)"
echo "$MSG" | tee -a "$LOG_FILE"
ALERTES=$((ALERTES + 1))
else
echo " ✅ $MONTAGE : ${POURCENTAGE}%" | tee -a "$LOG_FILE"
fi
done < <(df -h | tail -n +2)
echo "---" | tee -a "$LOG_FILE"
echo "Bilan : $ALERTES partition(s) en alerte" | tee -a "$LOG_FILE"
# Code de sortie non-nul si des alertes existent (utile pour le monitoring)
[[ $ALERTES -eq 0 ]]
EOF
chmod +x 04-surveiller-disques.sh
# Tests
./04-surveiller-disques.sh 1 # Seuil très bas = beaucoup d'alertes
./04-surveiller-disques.sh 95 # Seuil très haut = probablement aucune alerte
echo "Code de sortie : $?"✅ Vérification du résultat
./01-structure.shsans argument retourne exit code 1./01-structure.sh aliceaffiche "Bonjour, alice !"./02-conditions.sh /etc/hostsidentifie correctement le type de fichier./03-boucles.shaffiche l'état des services et le bilan disques./04-surveiller-disques.sh 1génère des alertes et exit code 1
💡 À retenir
Les 3 options indispensables :
| Option | Effet |
|---|---|
set -e | Le script s'arrête à la première commande qui échoue |
set -u | Erreur si une variable non définie est utilisée |
set -o pipefail | cmd1 | cmd2 échoue si cmd1 échoue (pas seulement cmd2) |
Tests de fichiers courants :
-f: fichier normal,-d: répertoire,-L: lien symbolique-r/-w/-x: lisible / modifiable / exécutable-z: chaîne vide,-n: chaîne non vide
✨ Solution Complète
#!/usr/bin/env bash
set -euo pipefail
# Validation
[[ $# -ge 1 ]] || { echo "Usage: $0 <arg>" >&2; exit 1; }
# Variables
NOM="${1}"
VALEUR="${2:-valeur_par_defaut}"
# Condition
if [[ -f "$NOM" ]]; then
echo "C'est un fichier"
elif [[ -d "$NOM" ]]; then
echo "C'est un répertoire"
fi
# Boucle
for ITEM in un deux trois; do
echo "$ITEM"
done