Vous avez maintenant trois petits scripts qui marchent. Mais ils se ressemblent : chacun recopie la même façon de lire un argument, d’afficher une erreur, de s’arrêter. Et aucun ne résiste vraiment quand les choses tournent mal — une variable mal orthographiée, un dossier supprimé en cours de route, un maillon de pipe qui échoue en silence. Ce tutoriel fait passer vos scripts du stade « bricolage qui fonctionne » au stade « outil robuste sur lequel on peut compter ». Deux leviers pour ça : les fonctions, qui éliminent la répétition, et le mode strict, qui transforme les bugs silencieux en erreurs visibles.
Dans le fil rouge, Boubacar rassemble ses outils épars en un seul script propre, atelier.sh, doté de fonctions réutilisables et d’options en ligne de commande (-d, -v, -h). C’est le tournant où sa boîte à outils devient un vrai petit logiciel.
🎯 Ce que vous allez apprendre
- Définir des fonctions, leur passer des arguments (
$1,$@) et isoler leurs variables aveclocal; - Activer le mode strict
set -euo pipefailet comprendre ce que chaque option protège (et ses pièges) ; - Gérer des options en ligne de commande avec
getopts(-d dossier,-v,-h) ; - Nettoyer proprement à la sortie avec
trap, même en cas d’erreur.
🛠️ Ce que vous allez construire
Un script atelier.sh structuré en fonctions (journalisation, vérification, aide), piloté par des options, protégé par le mode strict et un trap de nettoyage. Le squelette professionnel que vous réutiliserez pour tous vos futurs scripts.
Prérequis
- Avoir suivi les trois premiers tutoriels de la série (variables, conditions, boucles).
- Test express : si vous savez écrire une boucle
foret unif [[ ]]avec unexit, vous êtes prêt. - Bash 4+ (testé sous Bash 5.3). ⏱️ ~40 minutes.
Étape 1 — Définir et appeler une fonction
Une fonction est un bloc de code nommé qu’on appelle autant de fois qu’on veut. Elle évite de recopier les mêmes lignes et donne un nom parlant à une opération. La déclaration est simple : un nom, des parenthèses vides, des accolades. On appelle la fonction comme une commande, par son nom.
journal() {
echo "[$(date '+%H:%M:%S')] $1"
}
journal "Démarrage du script"
journal "Traitement en cours"
La fonction journal préfixe chaque message d’un horodatage. On l’appelle ensuite comme n’importe quelle commande : journal "mon message". À l’intérieur, $1 est le premier argument de la fonction (pas du script) — ici le message. Ce simple réflexe — une fonction pour journaliser — rend déjà tout le script plus lisible et cohérent.
Étape 2 — Arguments, valeur de retour et variables locales
Une fonction reçoit ses arguments exactement comme un script : $1, $2, $@ (tous les arguments), $# (leur nombre). Point crucial : par défaut, une variable créée dans une fonction est globale et peut écraser une variable du reste du script. Le mot-clé local la confine à la fonction. Prenez l’habitude de déclarer local systématiquement.
compter_fichiers() {
local dossier="$1"
local n
n=$(find "$dossier" -type f | wc -l)
echo "$n" # une fonction "renvoie" une donnée en l'affichant
}
total=$(compter_fichiers "/var/www/boutique-aminata")
echo "Fichiers : $total"
Ici dossier et n sont locaux : ils n’existent que le temps de la fonction. Pour renvoyer une donnée (un nombre, du texte), une fonction l’affiche avec echo, et l’appelant la capture avec $(...) — exactement comme pour une commande. Attention à ne pas confondre avec return : return ne renvoie qu’un code de sortie (0–255), pas une donnée. On utilise return pour dire « succès/échec », echo pour transmettre une valeur.
✅ Point d’étape — Vous savez écrire une fonction, lui passer des arguments, isoler ses variables avec
local, et récupérer son résultat avec$(...). Testezcompter_fichierssur un dossier réel.
Étape 3 — Le mode strict set -euo pipefail
Par défaut, Bash est trop indulgent : il continue après une commande qui échoue, accepte une variable jamais définie comme si elle était vide, et ignore l’échec d’un maillon au milieu d’un pipe. Ces tolérances cachent des bugs. Le mode strict, posé en tête de script, les transforme en arrêts nets et visibles.
#!/usr/bin/env bash
set -euo pipefail
Trois protections, à comprendre une par une. -e : le script s’arrête dès qu’une commande échoue (code non nul), au lieu de continuer en cascade. -u : utiliser une variable non définie devient une erreur — adieu les fautes de frappe du genre $doss au lieu de $dossier qui s’évaporaient en silence. -o pipefail : dans a | b | c, si n’importe quel maillon échoue, tout le pipe est considéré comme échoué (par défaut, seul le dernier compte).
Un avertissement honnête : -e a des angles morts. Une commande dans un if, ou suivie de ||, ne déclenche pas l’arrêt (c’est voulu). Et certaines constructions le désactivent localement. Quand vous voulez qu’une commande puisse échouer sans tuer le script, ajoutez explicitement || true :
grep "motif-peut-etre-absent" fichier.log || true
Sans le || true, un grep qui ne trouve rien (code 1) ferait planter le script en mode strict. Cette ligne dit « cet échec-là est normal, continue ». Le mode strict n’est pas magique, mais il attrape l’écrasante majorité des bugs idiots avant qu’ils ne fassent des dégâts.
Étape 4 — Nettoyer à la sortie avec trap
Un script sérieux crée souvent des fichiers temporaires. S’il s’arrête en plein milieu (erreur, Ctrl+C), ces fichiers traînent. trap résout ça : il enregistre une commande à exécuter quoi qu’il arrive au moment où le script se termine. On l’associe au signal EXIT, qui couvre la fin normale comme l’interruption.
fichier_temp=$(mktemp)
trap 'rm -f "$fichier_temp"' EXIT
echo "Travail temporaire..." > "$fichier_temp"
# ... le script peut planter ici, le fichier sera quand même supprimé
mktemp crée un fichier temporaire au nom unique (jamais de collision). trap 'rm -f "$fichier_temp"' EXIT garantit sa suppression à la sortie, succès ou échec. C’est le réflexe propre : on n’oublie jamais de faire le ménage, même quand le script meurt brutalement. Le même mécanisme sert à fermer une connexion, libérer un verrou, ou restaurer un état.
EXIT couvre déjà l’interruption clavier, mais on peut aussi capter des signaux précis pour réagir différemment. INT est l’interruption par Ctrl+C, TERM la demande d’arrêt envoyée par le système (par exemple kill). Les intercepter permet d’afficher un message propre avant de quitter.
trap 'echo "Interruption demandée, on s'\''arrête proprement." >&2; exit 130' INT TERM
Ici, un Ctrl+C n’arrête plus le script de façon brutale : il affiche un message et quitte avec le code 130 (la convention pour « terminé par Ctrl+C »). Combiné au trap ... EXIT qui nettoie, votre script se comporte proprement quelle que soit la façon dont il se termine — un signe distinctif des outils sérieux.
Un dernier détail sur les arguments, qui mord en silence : "$@" et "$*" ne sont pas équivalents. "$@" préserve chaque argument comme un élément distinct (un nom à espaces reste entier), tandis que "$*" colle tous les arguments en une seule chaîne. Pour transmettre les arguments d’un script à une commande, c’est toujours "$@" qu’on veut — c’est pourquoi notre point d’entrée s’écrira main "$@".
Étape 5 — Des options en ligne de commande avec getopts
Jusqu’ici nos scripts lisaient un argument positionnel ($1). Pour un vrai outil, on veut des options nommées : -d pour le dossier, -v pour le mode verbeux, -h pour l’aide. Bash fournit getopts, qui les analyse proprement, dans n’importe quel ordre. On l’utilise dans une boucle while couplée à un case.
verbeux=0
dossier=""
while getopts ":d:vh" opt; do
case "$opt" in
d) dossier="$OPTARG" ;;
v) verbeux=1 ;;
h) usage; exit 0 ;;
:) echo "L'option -$OPTARG attend un argument." >&2; exit 1 ;;
\?) echo "Option inconnue : -$OPTARG" >&2; exit 1 ;;
esac
done
shift $(( OPTIND - 1 ))
La chaîne ":d:vh" décrit les options : d: signifie « -d attend un argument » (le deux-points qui suit), v et h sont de simples drapeaux. Le deux-points en tête active la gestion silencieuse des erreurs, ce qui donne accès aux cas : (argument manquant) et \? (option inconnue). $OPTARG contient l’argument de l’option courante. Enfin, shift $(( OPTIND - 1 )) retire les options traitées, laissant les éventuels arguments positionnels restants dans $1, $2…
✅ Point d’étape — Vous savez parser des options avec
getopts, gérer les erreurs d’usage, et protéger le script avec le mode strict et untrap. Il reste à tout assembler.
Étape 6 — Assembler atelier.sh
On réunit fonctions, mode strict, trap et getopts dans un script structuré. La convention professionnelle : déclarer les fonctions en haut, puis une fonction main appelée tout en bas. C’est lisible, testable, et c’est le modèle que vous garderez.
#!/usr/bin/env bash
#
# atelier.sh — boîte à outils de vérification de sites.
# Usage : ./atelier.sh -d /var/www/boutique-aminata [-v]
# Boîte à outils Atelier — tutoriel 4.
set -euo pipefail
usage() {
cat <<EOF
Usage : $0 -d DOSSIER [-v] [-h]
-d DOSSIER dossier du site à vérifier (obligatoire)
-v mode verbeux
-h affiche cette aide
EOF
}
journal() {
(( verbeux )) && echo "[$(date '+%H:%M:%S')] $*"
return 0
}
erreur() {
echo "❌ $*" >&2
}
verifier_site() {
local dossier="$1"
[[ -d "$dossier" ]] || { erreur "Dossier absent : $dossier"; return 1; }
[[ -s "$dossier/config.php" ]] || { erreur "Config absente : $dossier/config.php"; return 2; }
journal "Site $dossier : structure valide."
return 0
}
main() {
verbeux=0
local dossier=""
while getopts ":d:vh" opt; do
case "$opt" in
d) dossier="$OPTARG" ;;
v) verbeux=1 ;;
h) usage; exit 0 ;;
:) erreur "L'option -$OPTARG attend un argument."; exit 1 ;;
\?) erreur "Option inconnue : -$OPTARG"; exit 1 ;;
esac
done
[[ -n "$dossier" ]] || { erreur "Option -d obligatoire."; usage; exit 1; }
journal "Vérification de $dossier"
if verifier_site "$dossier"; then
echo "✅ $dossier est prêt."
else
erreur "Vérification échouée (code $?)."
exit 1
fi
}
main "$@"
Observez l’architecture : usage, journal, erreur et verifier_site sont des fonctions autonomes ; main "$@" tout en bas reçoit les arguments du script et orchestre le tout. journal n’affiche qu’en mode verbeux grâce à (( verbeux )). verifier_site renvoie un code distinct par type d’échec, que main peut inspecter. C’est exactement le squelette d’un outil professionnel — vous le réutiliserez pour tout.
✅ Point d’étape final —
./atelier.sh -haffiche l’aide ;./atelier.shsans option refuse avec « -d obligatoire » ;./atelier.sh -d /tmp -vjournalise puis échoue proprement sur la config absente. Trois comportements, un seul script bien structuré.
🐞 Pièges fréquents
| Symptôme / erreur | Cause probable | Correctif |
|---|---|---|
unbound variable avec set -u |
Variable utilisée avant d’être définie (souvent une faute de frappe) | Définir la variable, ou ${var:-valeur} pour un défaut sûr |
Le script s’arrête sur un grep sans résultat |
set -e + commande qui échoue normalement |
Ajouter || true à la commande tolérée |
| Une variable de fonction écrase une globale | Oubli de local |
Déclarer local chaque variable de fonction |
getopts ignore les options |
Oubli de shift $((OPTIND-1)) ou mauvaise chaîne d’options |
Vérifier la chaîne ":d:vh" et le shift |
| Fichiers temporaires qui s’accumulent | Pas de trap ... EXIT |
Nettoyer via trap 'rm -f "$tmp"' EXIT |
🌍 Adaptation au contexte ouest-africain
Un script robuste, c’est de la sérénité quand on administre des serveurs à distance avec une connexion instable. Si la liaison saute en plein script, le mode strict évite qu’une commande à moitié exécutée laisse le site dans un état incohérent, et le trap nettoie les fichiers temporaires. Pour les freelances qui jonglent entre plusieurs clients, un script structuré en fonctions est aussi un capital : on copie atelier.sh, on adapte une fonction, et on a un nouvel outil en dix minutes. Pensez à valider vos scripts avec ShellCheck (0.11.0), qui tourne en ligne sans rien installer — utile quand la bande passante est limitée.
✅ Récapitulatif
Vos scripts sont passés à l’âge adulte. Vous savez factoriser le code en fonctions avec arguments et variables local, distinguer return (code de sortie) de l’affichage qui transmet une donnée, et activer le mode strict set -euo pipefail en connaissant ses limites (|| true pour les échecs attendus). Vous nettoyez derrière vous avec trap ... EXIT et offrez une vraie interface en ligne de commande avec getopts. atelier.sh est désormais un squelette professionnel réutilisable.
🧾 Aide-mémoire
| Élément | Rôle |
|---|---|
nom() { ... } |
Définit une fonction |
local x="$1" |
Variable confinée à la fonction |
set -euo pipefail |
Mode strict : arrêt sur erreur, variable non définie, pipe |
cmd || true |
Tolère l’échec d’une commande en mode strict |
trap 'rm -f "$tmp"' EXIT |
Nettoyage garanti à la sortie |
while getopts ":d:vh" opt |
Analyse les options en ligne de commande |
shift $(( OPTIND - 1 )) |
Retire les options traitées |
main "$@" |
Point d’entrée recevant tous les arguments |
💪 À vous de jouer
Ajoutez à atelier.sh une option -s SEUIL qui fixe l’espace disque minimal exigé (en Go), et intégrez la vérification du disque vue au tutoriel 2 dans la fonction verifier_site.
Voir une solution
# dans la chaîne getopts : ":d:s:vh"
# nouveau cas :
s) seuil="$OPTARG" ;;
# valeur par défaut avant getopts : seuil=2
# dans verifier_site, après les tests existants :
local libre
libre=$(df -BG --output=avail "$dossier" | awk 'NR==2 {gsub(/G/,""); print $1}')
(( libre >= seuil )) || { erreur "Disque insuffisant : ${libre} Go (< ${seuil})"; return 3; }
Tutoriels frères
- Boucles et traitement de fichiers en Bash — le traitement par lot à factoriser en fonctions.
- Traiter du texte : grep, sed et awk — extraire de l’information de fichiers et de logs.
Pour aller plus loin
- 🔝 Retour au guide principal : Bash scripting : le guide complet.
- Prochain tutoriel conseillé : grep, sed et awk.
- GNU Bash Manual — Shell Functions et la page
setdu manuel.
FAQ
Q : function nom { ou nom() { ?
R : Les deux marchent en Bash, mais nom() { ... } est la forme POSIX, portable et recommandée. Évitez de mélanger function et les parenthèses.
Q : Le mode strict casse mon vieux script. Que faire ?
R : Introduisez-le progressivement. Commencez par set -u seul (faciles à corriger : les variables non définies), puis ajoutez -o pipefail, et enfin -e en traitant les échecs attendus avec || true.
Q : Pourquoi ma fonction « ne renvoie pas » la valeur attendue avec return ?
R : return ne transmet qu’un code de sortie entre 0 et 255, pas une chaîne ni un grand nombre. Pour renvoyer une donnée, affichez-la avec echo et capturez-la avec $(ma_fonction).
Q : getopts gère-t-il les options longues comme --dossier ?
R : Non, getopts ne gère que les options courtes (-d). Pour les options longues, on écrit un parseur manuel avec une boucle while et case, ou on utilise getopt (sans s, l’utilitaire externe), plus capricieux.
📚 Ressources et références
- GNU Bash Manual — The Set Builtin : le détail de
-e,-u,pipefail. - BashFAQ 105 — Why doesn’t set -e do what I expected : les angles morts de
-eexpliqués. - Bash pour sysadmin : sauvegardes, monitoring, déploiements — ces patterns appliqués à des scripts de production.
Mots-clés : fonctions bash, set -euo pipefail, getopts, trap bash, mode strict bash, scripts robustes, variables locales bash, atelier.sh.