Organiser son code avec des fonctions
Notions théoriques
Comme en PHP (que vous avez déjà pratiqué), une fonction permet de regrouper un bloc de code sous un nom, pour :
- éviter de dupliquer le même code à plusieurs endroits,
- donner un nom clair à une action (intention explicite),
- pouvoir tester et faire évoluer chaque morceau du script indépendamment.
Déclarer une fonction
ma_fonction() {
echo "Bonjour depuis ma_fonction"
}
Vous croiserez aussi la variante function ma_fonction { ... }, strictement équivalente. La forme nom() { ... } est la plus répandue.
Une fonction Bash doit être définie avant d'être appelée dans le fichier. Convention à adopter : placez toutes vos fonctions en haut du script, et le « programme principal » (les instructions réellement exécutées dans l'ordre) tout en bas.
Appeler une fonction
ma_fonction
On appelle une fonction Bash comme une commande : sans parenthèses ni virgules. ma_fonction() (avec les parenthèses vides) ne l'appelle pas, c'est uniquement la syntaxe de sa déclaration.
Paramètres d'une fonction
À l'intérieur d'une fonction, $1, $2, $#, "$@" désignent les arguments de la fonction, pas ceux du script :
afficher_message() {
echo "Message : $1"
}
afficher_message "Sauvegarde démarrée" # $1 vaut ici "Sauvegarde démarrée"
Si le script lui-même a été lancé avec des arguments (./script.sh a b), ces $1/$2 du script ne sont pas automatiquement visibles dans la fonction : il faut les lui passer explicitement, comme dans l'exemple ci-dessus.
Variables locales
Par défaut, une variable créée dans une fonction est globale : elle peut écraser une variable de même nom utilisée ailleurs dans le script. Pour éviter cet effet de bord, utilisez local :
calculer_taille() {
local dossier="$1"
local taille=$(du -sh "$dossier" | cut -f1)
echo "$taille"
}
Renvoyer un résultat
return renvoie un code de retour (un nombre entre 0 et 255), pas une valeur quelconque — exactement comme pour un script entier avec exit :
verifier_dossier() {
if [ -d "$1" ]; then
return 0
else
return 1
fi
}
if verifier_dossier "/etc"; then
echo "Le dossier existe"
fi
Pour qu'une fonction « renvoie » une donnée (une chaîne, un nombre calculé...), la convention est de l'afficher avec echo, et de récupérer ce résultat depuis l'extérieur avec $(...) :
calculer_taille() {
local dossier="$1"
du -sh "$dossier" | cut -f1
}
taille=$(calculer_taille "/var/log")
echo "Taille : $taille"
Fonctions utilitaires typiques d'un script d'administration
usage() {
echo "Usage : $0 <dossier>"
}
log_info() {
echo "[$(date +%H:%M:%S)] $1"
}
log_erreur() {
echo "[$(date +%H:%M:%S)] ERREUR : $1" >&2
return 1
}
>&2 redirige l'affichage vers la sortie d'erreur plutôt que la sortie standard — pratique pour séparer messages normaux et messages d'erreur (nous détaillerons cette redirection en séance 7).
Exemple pratique
#!/bin/bash
# Script : sauvegarde.sh
# Rôle : sauvegarder un dossier (version structurée en fonctions)
usage() {
echo "Usage : $0 <dossier>"
}
log_info() {
echo "[$(date +%H:%M:%S)] $1"
}
verifier_dossier() {
local dossier="$1"
if [ -d "$dossier" ]; then
return 0
else
return 1
fi
}
archiver() {
local dossier="$1"
local nom_archive
nom_archive=$(basename "$dossier")
tar czf "/tmp/sauvegardes/${nom_archive}.tar.gz" "$dossier"
}
# --- Programme principal ---
if [ $# -eq 0 ]; then
usage
exit 1
fi
if verifier_dossier "$1"; then
log_info "Dossier valide : $1"
archiver "$1"
log_info "Sauvegarde terminée"
else
log_info "Dossier invalide : $1"
exit 1
fi
Remarquez comme le bloc « Programme principal » est court et lisible : il se contente d'appeler des fonctions bien nommées, sans détail d'implémentation. C'est tout l'intérêt de découper son script en fonctions.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Nous refactorisons sauvegarde.sh en fonctions bien nommées, pour préparer la dernière séance consacrée à la fiabilité.
Étape 1 — Écrire la fonction usage()
Regroupez le message d'usage dans une fonction unique usage(), plutôt que de le recopier à chaque endroit du script où une erreur d'utilisation peut survenir.
Étape 2 — Écrire la fonction log_info()
Préfixer chaque message par l'heure rend un script exploitable en production : en cas de problème, vous savez exactement quand chaque étape s'est déroulée, surtout si les messages sont un jour redirigés vers un fichier de log.
Étape 3 — Transformer la vérification en fonction
Chaque fonction devrait faire une seule chose et bien la faire : verifier_dossier vérifie, elle n'affiche rien et ne modifie rien. C'est ce qui permet de la réutiliser ailleurs sans effet de bord inattendu.
Étape 4 — Constater la portée d'une variable locale
Prenez l'habitude de déclarer local systématiquement pour toute variable créée à l'intérieur d'une fonction, sauf si vous avez une raison précise de vouloir la rendre globale.