Aller au contenu principal

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.

attention

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
attention

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
astuce

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


Comment appelle-t-on la fonction sauvegarder avec le paramètre /etc ?


À quoi correspond $1 à l'intérieur d'une fonction ?


Que renvoie réellement return 1 dans une fonction Bash ?


À quoi sert le mot-clé local dans une fonction ?


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()


Bonne pratique - Une seule source de vérité

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()


Bonne pratique - Horodater ses messages

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


Bonne pratique - Une fonction, une responsabilité

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


Bonne pratique - local par défaut

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.

📌 Une solution