Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Fonctions Bash : déclarer, appeler, retourner

Par mis à jour le

Améliorer cette page

Une fonction Bash est un bloc de commandes nommé, réutilisable à l'intérieur du script. Sans fonctions, chaque logique répétée (validation d'argument, affichage d'erreur, transformation de chaîne) doit être dupliquée. Avec des fonctions bien découpées, un script de 200 lignes devient lisible et testable, et les fonctions utilitaires se partagent entre scripts via source.

  • Déclarer et appeler une fonction avec et sans le mot-clé function
  • Accéder aux arguments d'une fonction via $1, $2, $@
  • Utiliser local pour éviter de polluer les variables globales
  • Retourner une valeur par echo capturé avec $()
  • Renvoyer un code de sortie utilisable dans un if
  • Organiser les fonctions utilitaires dans un fichier séparé et les charger avec source

Les fonctions apparaissent naturellement dès qu'un script dépasse une vingtaine de lignes :

  • encapsuler la logique de validation d'un argument dans verifier_arg() appelée pour chaque paramètre
  • centraliser la gestion des erreurs dans erreur() pour ne pas dupliquer le echo >&2; exit 1
  • extraire une transformation (récupérer l'extension d'un fichier, normaliser un nom) dans une fonction réutilisable
  • partager des fonctions de log (log_info, log_warn, log_error) entre plusieurs scripts grâce à source

L'objectif RHCSA « Create simple shell scripts » (EX200, RHEL 10) ne cite pas les fonctions : elles servent à rendre vos scripts lisibles et maintenables, pas à cocher une case de l'examen. Un script d'examen de trente lignes s'en passe très bien ; un script d'exploitation de deux cents lignes, non.

Deux syntaxes existent, et elles ne sont pas portables de la même façon. La forme nom() { ... } est celle de POSIX : Bash, dash et tous les shells la comprennent. Le mot-clé function est propre à Bash et ksh : mesuré sur Debian 13, dash le refuse avec Syntax error: "}" unexpected.

Fenêtre de terminal
# Syntaxe POSIX, recommandée
bonjour() {
echo "Bonjour depuis une fonction !"
}
# Syntaxe avec le mot-clé function (Bash et ksh, refusée par dash)
function bonjour {
echo "Bonjour depuis une fonction !"
}

L'appel s'écrit comme une commande, sans parenthèses :

Fenêtre de terminal
bonjour
# Bonjour depuis une fonction !

Une fonction doit être déclarée avant son premier appel : contrairement à certains langages, Bash exécute les fichiers de haut en bas, sans passe préalable. Appelée trop tôt, elle provoque un command not found.

Les fonctions reçoivent leurs arguments comme un script : $1, $2… $@, $#. Ce sont les paramètres de la fonction, distincts de ceux du script : les modifier dans la fonction (avec shift ou set --) ne touche pas aux arguments du script (mesuré).

Fenêtre de terminal
saluer() {
local prenom="$1"
local titre="${2:-M.}" # valeur par défaut si $2 absent
echo "Bonjour $titre $prenom"
}
saluer "Alice" # Bonjour M. Alice
saluer "Bob" "Dr." # Bonjour Dr. Bob

shift supprime $1 et décale tous les suivants d'un rang. Utile pour traiter le premier argument comme un titre, puis itérer sur les suivants :

Fenêtre de terminal
afficher_liste() {
local titre="$1"
shift # $2 devient $1, $3 devient $2...
echo "$titre :"
for item in "$@"; do
echo " - $item"
done
}
afficher_liste "Services" "nginx" "postgresql" "redis"
# Services :
# - nginx
# - postgresql
# - redis

Sans local, une affectation dans une fonction modifie la variable globale du même nom, un effet de bord difficile à suivre :

Fenêtre de terminal
nom="global"
modifier() {
nom="modifié dans la fonction" # modifie la variable globale !
}
modifier
echo "$nom" # modifié dans la fonction

Avec local, la portée est limitée à la fonction, et la globale reste intacte après l'appel :

Fenêtre de terminal
nom="global"
modifier() {
local nom="local à la fonction"
echo "Dans la fonction : $nom"
}
modifier
echo "Après : $nom"
# Dans la fonction : local à la fonction
# Après : global

Règle : déclarez toutes les variables internes d'une fonction avec local, c'est la forme défensive par défaut.

Bash ne dispose pas de return valeur_textuelle. La méthode standard consiste à echo la valeur dans la fonction, puis à la capturer avec $() à l'appel :

Fenêtre de terminal
extension() {
local fichier="$1"
echo "${fichier##*.}"
}
ext=$(extension "rapport.tar.gz")
echo "Extension : $ext"
# Extension : gz
Fenêtre de terminal
date_snapshot() {
date '+%Y%m%d_%H%M%S'
}
sauvegarde="/backup/app_$(date_snapshot).tar.gz"
echo "$sauvegarde"
# /backup/app_20260410_080812.tar.gz

Contrepartie à connaître : $( ) exécute la fonction dans un sous-shell. Une variable globale qu'elle modifie au passage est donc perdue au retour. Mesuré : avec f() { N=5; echo val; }, l'appel r=$(f) laisse N à 0, alors qu'un appel direct f le passe à 5.

Fenêtre de terminal
N=0
f() { N=5; echo "val"; }
r=$(f)
echo "N après \$(f) : $N" # N après $(f) : 0
f > /dev/null
echo "N après f : $N" # N après f : 5

return sans argument retourne le code de la dernière commande. return N retourne le code N (0 = succès, 1–255 = erreur). À ne pas confondre avec exit, qui arrête le script entier même appelé dans une fonction (mesuré : f() { exit 3; }; f termine le script avec le code 3).

Fenêtre de terminal
fichier_lisible() {
local f="$1"
[[ -r "$f" ]] # retourne 0 si lisible, 1 sinon
}
if fichier_lisible "/etc/passwd"; then
echo "/etc/passwd : lisible"
fi
if ! fichier_lisible "/etc/shadow"; then
echo "/etc/shadow : non lisible (normal en user)"
fi
# /etc/passwd : lisible
# /etc/shadow : non lisible (normal en user)

Ce patron est très utilisé pour valider des préconditions avant d'agir, sur une seule ligne lisible :

Fenêtre de terminal
service_actif() {
systemctl is-active --quiet "$1"
}
service_actif "nginx" || { echo "nginx arrêté, abandon." >&2; exit 1; }

Bash ne supporte pas les retours multiples. La convention consiste à affecter des variables globales avec un préfixe explicite, en appelant la fonction directement, pas dans $( ) :

Fenêtre de terminal
calculer() {
local a="$1"
local b="$2"
RESULT_SOMME=$((a + b))
RESULT_PRODUIT=$((a * b))
}
calculer 6 7
echo "Somme=$RESULT_SOMME, Produit=$RESULT_PRODUIT"
# Somme=13, Produit=42

Une fonction erreur() centralisée évite de dupliquer echo >&2; exit 1 partout, et garantit que tous les messages partent sur la sortie d'erreur avec le même format :

Fenêtre de terminal
erreur() {
echo "ERREUR : $*" >&2
exit 1
}
verifier_arg() {
local val="$1"
local nom="$2"
[[ -n "$val" ]] || erreur "L'argument '$nom' est obligatoire."
}
verifier_arg "monapp" "APP_NAME" # OK
verifier_arg "" "APP_NAME" # ERREUR : L'argument 'APP_NAME' est obligatoire.

$* dans erreur() concatène tous les arguments en une seule chaîne, pratique pour un message d'erreur composé.

Trois fonctions d'une ligne suffisent à horodater les messages et à séparer les niveaux : l'information part sur la sortie standard, les avertissements et erreurs sur la sortie d'erreur.

Fenêtre de terminal
log_info() { echo "[INFO] $(date '+%H:%M:%S') $*"; }
log_warn() { echo "[WARN] $(date '+%H:%M:%S') $*" >&2; }
log_error() { echo "[ERROR] $(date '+%H:%M:%S') $*" >&2; }
log_info "Démarrage de l'application"
log_warn "Configuration par défaut utilisée"
log_error "Connexion à la base échouée"
# [INFO] 08:08:12 Démarrage de l'application
# [WARN] 08:08:12 Configuration par défaut utilisée
# [ERROR] 08:08:12 Connexion à la base échouée

source (ou .) charge et exécute un fichier dans le contexte du shell courant : ses fonctions deviennent disponibles immédiatement, comme si elles étaient écrites dans le script :

lib/utils.sh
erreur() { echo "ERREUR : $*" >&2; exit 1; }
log_info() { echo "[INFO] $(date '+%H:%M:%S') $*"; }
log_error() { echo "[ERROR] $(date '+%H:%M:%S') $*" >&2; }
mon_script.sh
#!/bin/bash
set -euo pipefail
# shellcheck source=lib/utils.sh
source "$(dirname "$0")/lib/utils.sh"
log_info "Script démarré"
[[ $# -ge 1 ]] || erreur "Usage : $(basename "$0") <argument>"
log_info "Argument : $1"

$(dirname "$0") pointe vers le répertoire du script, la bibliothèque est donc trouvée même si le script est appelé depuis un autre répertoire, par cron par exemple. Le commentaire # shellcheck source= aide l'analyse statique à suivre le fichier.

Les erreurs de fonctions tiennent presque toutes à la portée : ce que la fonction voit, ce qu'elle modifie, et ce qui survit à l'appel. Les cas ci-dessous ont été reproduits sur Bash 5.2.

SymptômeCause probableSolution
Variable non modifiée après l'appelDéclarée avec local dans la fonctionUtiliser une variable globale avec un préfixe explicite (RESULT_)
command not found à l'appelFonction déclarée après l'appelDéplacer la déclaration avant le premier appel
$(fonction) retourne videLa fonction utilise return au lieu de echoecho la valeur ; return ne sert qu'aux codes de sortie
Une globale modifiée par la fonction reste inchangéeFonction appelée dans $( ), donc dans un sous-shellAppeler la fonction directement, ou retourner la valeur par echo
Le script entier s'arrête dans une fonctionexit au lieu de returnreturn N ne sort que de la fonction
source lib.sh : No such fileChemin relatif au répertoire courantUtiliser source "$(dirname "$0")/lib.sh"
  • La syntaxe nom_fonction() { ... } est la forme POSIX, recommandée en Bash.
  • Déclarez toujours les variables internes avec local, c'est la forme défensive par défaut.
  • Pour retourner une valeur textuelle : echo dans la fonction + $(fonction) à l'appel, sachant que $( ) lance un sous-shell.
  • Pour retourner un booléen : la fonction se termine par une commande ou un test [[ ]], utilisable directement dans if.
  • source charge une bibliothèque de fonctions dans le shell courant, le chemin doit être basé sur $(dirname "$0").

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn