DevOpsFacile
AccueilParcours de formationCertificationsModulesCheat SheetÀ Propos
DevOpsFacile

Une plateforme d'apprentissage complète pour maîtriser les pratiques DevOps modernes, du débutant à l'expert.

contact@devopsfacile.fr

Formation

  • Parcours de formation
  • Modules

Informations

  • À Propos
  • Conditions d'utilisation
  • Confidentialité
  • Mentions légales

© 2026 DevOps Facile. Tous droits réservés.

Fait avec pour la communauté DevOps

ModulesScripting Bash et administration système Linux10 - Bash avancé : fonctions, tableaux et gestion d'erreurs

Détails

  • 35 minutes
  • Avancé

Objectifs

  • Créer des fonctions Bash réutilisables avec retour de valeurs
  • Utiliser les tableaux associatifs (dictionnaires) en Bash
  • Implémenter un gestionnaire d'erreurs avec trap
  • Structurer un script complexe avec plusieurs fonctions
Module Scripting Bash et administration système Linux

Exercice 10 : Bash avancé : fonctions, tableaux et gestion d'erreurs

🎯 Objectifs

À la fin de cet exercice, vous serez capable de :

  • ✅ Définir et appeler des fonctions Bash avec arguments et valeurs de retour
  • ✅ Utiliser les tableaux indexés et associatifs (declare -a et declare -A)
  • ✅ Capturer les erreurs avec trap ERR et nettoyer avec trap EXIT
  • ✅ Utiliser les sous-shells $() et les redirections avancées
  • ✅ Écrire un script de déploiement structuré avec rollback automatique

Durée estimée : 35 minutes

Difficulté : ⭐⭐⭐☆☆ (Avancé)

Prérequis : Exercice 09 complété - variables, conditions, boucles


📖 Contexte

Les fonctions transforment un script de 200 lignes en code lisible et maintenable. Elles permettent la réutilisation, le test unitaire de chaque partie, et la décomposition du problème. Le trap garantit qu'un script ne laisse jamais de fichiers temporaires ou de ressources verrouillées derrière lui, même en cas d'erreur.


📋 Énoncé

Construisez un script de déploiement avec fonctions, gestion d'erreurs et rollback automatique.


🧭 Déroulement de l'exercice

Tâche 1 : Fonctions de base avec retour de valeurs

bash
cat > 01-fonctions.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail

# Fonction avec arguments ($1, $2...) et return (seulement 0-255)
# Pour retourner une chaîne, on utilise echo + sous-shell $()
verifier_port() {
  local HOST="${1:?verifier_port: host manquant}"
  local PORT="${2:?verifier_port: port manquant}"
  local TIMEOUT="${3:-3}"  # Valeur par défaut : 3 secondes

  if timeout "$TIMEOUT" bash -c "echo > /dev/tcp/$HOST/$PORT" 2>/dev/null; then
    echo "ouvert"
    return 0
  else
    echo "fermé"
    return 1
  fi
}

# Appel avec capture de la sortie
ETAT_HTTP=$(verifier_port "google.com" 80)
echo "Port 80 google.com : $ETAT_HTTP"

ETAT_LOCAL=$(verifier_port "localhost" 22 || true)
echo "Port 22 localhost : $ETAT_LOCAL"

# Fonction qui retourne une valeur calculée
calculer_taille_repertoire() {
  local CHEMIN="${1:?chemin requis}"
  [[ -d "$CHEMIN" ]] || { echo "0"; return 1; }
  du -sh "$CHEMIN" 2>/dev/null | cut -f1
}

echo "Taille /etc : $(calculer_taille_repertoire /etc)"
echo "Taille /tmp : $(calculer_taille_repertoire /tmp)"
EOF
chmod +x 01-fonctions.sh && ./01-fonctions.sh

Indice : local est indispensable dans les fonctions. Sans local, une variable créée dans une fonction est globale et peut écraser une variable du même nom dans le script principal.


Tâche 2 : Tableaux indexés et associatifs

bash
cat > 02-tableaux.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail

# --- Tableau indexé ---
echo "=== Tableau indexé ==="
SERVEURS=("web-01" "web-02" "db-01" "cache-01")
echo "Nombre de serveurs : ${#SERVEURS[@]}"
echo "Premier : ${SERVEURS[0]}"
echo "Dernier : ${SERVEURS[-1]}"

# Itérer
for SERVEUR in "${SERVEURS[@]}"; do
  echo "  Ping $SERVEUR..."
done

# Ajouter
SERVEURS+=("backup-01")
echo "Après ajout : ${SERVEURS[*]}"

# Slice (éléments 1 à 3)
echo "Slice [1..3] : ${SERVEURS[@]:1:3}"

# --- Tableau associatif (dictionnaire) ---
echo ""
echo "=== Tableau associatif ==="
declare -A CONFIG=(
  ["env"]="production"
  ["region"]="eu-west-3"
  ["replicas"]="3"
  ["debug"]="false"
)

# Lire une valeur
echo "Environnement : ${CONFIG[env]}"
echo "Région : ${CONFIG[region]}"

# Toutes les clés
echo "Clés : ${!CONFIG[@]}"

# Toutes les valeurs
echo "Valeurs : ${CONFIG[@]}"

# Itérer sur clés et valeurs
for CLE in "${!CONFIG[@]}"; do
  echo "  $CLE = ${CONFIG[$CLE]}"
done

# Vérifier si une clé existe
if [[ -v CONFIG["debug"] ]]; then
  echo "Debug mode : ${CONFIG[debug]}"
fi
EOF
chmod +x 02-tableaux.sh && ./02-tableaux.sh

Tâche 3 : Gestion d'erreurs avec trap

bash
cat > 03-trap.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail

# Fichiers temporaires - doivent être nettoyés même en cas d'erreur
TMP_DIR=$(mktemp -d)
TMP_FILE=$(mktemp)
LOCK_FILE="/tmp/mon-script.lock"

# --- NETTOYAGE AUTOMATIQUE ---
# trap s'exécute à la sortie du script (normale ou erreur)
cleanup() {
  local EXIT_CODE="$?"
  echo "Nettoyage en cours..."
  rm -rf "$TMP_DIR" "$TMP_FILE" 2>/dev/null || true
  rm -f "$LOCK_FILE" 2>/dev/null || true
  if [[ $EXIT_CODE -ne 0 ]]; then
    echo "Script terminé avec erreur (code: $EXIT_CODE)" >&2
  fi
}
trap cleanup EXIT

# --- GESTIONNAIRE D'ERREUR ---
erreur_handler() {
  local LINE="${BASH_LINENO[0]}"
  local CMD="${BASH_COMMAND}"
  echo "❌ Erreur ligne $LINE : '$CMD'" >&2
}
trap erreur_handler ERR

# --- GESTION DU VERROU (éviter les exécutions parallèles) ---
if [[ -f "$LOCK_FILE" ]]; then
  echo "Erreur : le script est déjà en cours (lock: $LOCK_FILE)" >&2
  exit 1
fi
touch "$LOCK_FILE"

# Corps du script
echo "Travail dans $TMP_DIR"
echo "données importantes" > "$TMP_DIR/output.txt"
echo "autres données" > "$TMP_FILE"

# Simuler un traitement
sleep 0.5
echo "Traitement terminé"
cat "$TMP_DIR/output.txt"

# Le cleanup() s'exécutera automatiquement en quittant
EOF
chmod +x 03-trap.sh && ./03-trap.sh

Vérification : Après l'exécution, ls /tmp/tmp* ne doit plus montrer les fichiers temporaires créés.


Tâche 4 : Script de déploiement avec rollback

bash
cat > 04-deploy.sh << 'EOF'
#!/usr/bin/env bash
# Script de déploiement avec rollback automatique
set -euo pipefail

APPLI="${1:-mon-app}"
VERSION="${2:-latest}"
DEPLOY_DIR="/opt/deploiements"
CURRENT_LINK="${DEPLOY_DIR}/current"

# Logging structuré
log() {
  local NIVEAU="$1"; shift
  echo "$(date '+%H:%M:%S') [$NIVEAU] $*"
}
log_info()  { log "INFO " "$@"; }
log_ok()    { log "OK   " "$@"; }
log_erreur(){ log "ERREUR" "$@" >&2; }

# Récupérer la version précédente pour le rollback
recuperer_version_precedente() {
  if [[ -L "$CURRENT_LINK" ]]; then
    basename "$(readlink "$CURRENT_LINK")"
  else
    echo ""
  fi
}

rollback() {
  local VERSION_PRECEDENTE
  VERSION_PRECEDENTE=$(recuperer_version_precedente)

  if [[ -z "$VERSION_PRECEDENTE" ]]; then
    log_erreur "Pas de version précédente pour rollback"
    return 1
  fi

  log_info "Rollback vers $VERSION_PRECEDENTE..."
  ln -sfn "${DEPLOY_DIR}/${VERSION_PRECEDENTE}" "$CURRENT_LINK"
  log_ok "Rollback effectué vers $VERSION_PRECEDENTE"
}

# En cas d'erreur, déclencher le rollback
trap 'rollback; exit 1' ERR

# --- DÉPLOIEMENT ---
log_info "Déploiement de $APPLI version $VERSION"
NOUVELLE_VERSION="${APPLI}-${VERSION}-$(date +%Y%m%d%H%M%S)"
CIBLE="${DEPLOY_DIR}/${NOUVELLE_VERSION}"

# Simuler la création du déploiement
mkdir -p "$CIBLE"
echo "$VERSION" > "$CIBLE/VERSION"
echo "{\"app\":\"$APPLI\",\"version\":\"$VERSION\"}" > "$CIBLE/config.json"

log_ok "Artefacts copiés dans $CIBLE"

# Simuler un healthcheck
log_info "Healthcheck..."
if [[ "$VERSION" == "broken" ]]; then
  log_erreur "Healthcheck échoué - rollback automatique !"
  exit 1  # Le trap ERR déclenche le rollback
fi

# Basculer le lien symbolique
ln -sfn "$CIBLE" "$CURRENT_LINK"
log_ok "Déploiement réussi : $CIBLE"
log_info "Version active : $(readlink -f "$CURRENT_LINK")"
EOF

chmod +x 04-deploy.sh
mkdir -p /opt/deploiements

# Premier déploiement
sudo ./04-deploy.sh mon-app 1.0.0

# Second déploiement (réussi)
sudo ./04-deploy.sh mon-app 1.1.0

# Déploiement cassé (doit rollback automatiquement vers 1.1.0)
sudo ./04-deploy.sh mon-app broken || true
sudo ls -la /opt/deploiements/current

✅ Vérification du résultat

  • 01-fonctions.sh affiche l'état du port 80 de google.com
  • 02-tableaux.sh affiche correctement les clés et valeurs du tableau associatif
  • 03-trap.sh nettoie les fichiers temporaires après exécution
  • 04-deploy.sh mon-app broken rollback automatiquement vers la version précédente

💡 À retenir

Fonctions Bash :

  • Toujours local pour les variables internes
  • Retourner une chaîne : echo "valeur" + capturer avec $(ma_fonction)
  • Retourner un code : return 0 (succès) ou return 1 (échec)

trap :

  • trap 'code' EXIT : s'exécute toujours en quittant (nettoyage)
  • trap 'code' ERR : s'exécute à chaque commande qui échoue
  • trap 'code' SIGINT SIGTERM : capture Ctrl+C et kill

✨ Solution Complète

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

# Nettoyage automatique
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT

# Fonction avec valeur de retour
get_info() {
  local FICHIER="${1:?fichier requis}"
  [[ -f "$FICHIER" ]] || return 1
  echo "$(wc -l < "$FICHIER") lignes"
}

echo "Taille: $(get_info /etc/passwd)"
Retour au module

Sur cette page

  • 🎯 Objectifs
  • 📖 Contexte
  • 📋 Énoncé
  • 🧭 Déroulement de l'exercice
  • Tâche 1 : Fonctions de base avec retour de valeurs
  • Tâche 2 : Tableaux indexés et associatifs
  • Tâche 3 : Gestion d'erreurs avec trap
  • Tâche 4 : Script de déploiement avec rollback
  • ✅ Vérification du résultat
  • 💡 À retenir
  • ✨ Solution Complète