Beaucoup de scripts Bash sont écrits dans un style strictement impératif : exécuter une commande, puis une autre, et ainsi de suite. Il arrive parfois que l'on ait besoin de regrouper une séquence de commandes qui, conceptuellement, remplissent un seul objectif. C'est là que les fonctions entrent en jeu.
On définit une fonction comme ceci :
my_function () {
COMMANDS
}
Les parenthèses vides indiquent simplement que l'on définit une fonction. On ne met rien à l'intérieur.
Une fois définies, les fonctions se comportent comme n'importe quelle autre commande (intégrée ou non).
Comme pour n'importe quelle commande, on peut fournir des arguments à ses fonctions.
À l'intérieur d'une fonction, on accède aux arguments à l'aide des paramètres positionnels, $1, $2, etc.
(Pour rappel, on a découvert les paramètres positionnels dans le concept Variables.)
Le paramètre spécial $0 n'est pas modifié à l'intérieur d'une fonction : il reste le nom du script en cours d'exécution.
La fonction en cours d'exécution peut accéder à son nom grâce à la variable $FUNCNAME.
Voir la section 3.4.2 Paramètres spéciaux du manuel.
On peut définir des variables à l'intérieur d'une fonction.
Si on les déclare avec la commande local, la portée de la variable se limite à la fonction courante (et aux fonctions qu'elle appelle).
Sinon, la variable est placée dans la portée globale.
Une variable locale peut porter le même nom qu'une variable globale. Dans ce cas, la variable locale « masque » la variable globale. Par exemple, une variable locale déclarée dans une fonction cache une variable globale du même nom : les références et les affectations portent sur la variable locale, ce qui laisse la variable globale inchangée. Quand la fonction se termine, la variable globale redevient visible.
x=5
myfunc () {
local x=100
echo "in my function, $x == 100"
}
echo "in the global scope, $x == 5"
myfunc
echo "back in the global scope, $x == 5"
Ce qui affiche :
in the global scope, 5 == 5
in my function, 100 == 100
back in the global scope, 5 == 5
À l'intérieur d'une fonction, on peut accéder aux variables de la portée de l'appelant. Cela signifie que l'on peut utiliser des variables globales, ainsi que des variables locales déclarées dans l'appelant (ou dans une fonction qui appelle l'appelant).
Techniquement, « globale » n'est pas le mot juste. Pour développer une variable dans une fonction, Bash remonte la pile d'appels, jusqu'à la portée globale, afin de trouver une fonction où ce nom de variable a été déclaré.
Cet exemple est adapté de la section Fonctions du shell du manuel :
func1() {
local var='func1 local'
func2
}
func2() {
echo "In func2, var = $var"
}
var=global
func1
func2
La sortie est :
In func2, var = func1 local
In func2, var = global
De même, affecter une valeur à une variable l'affecte dans la portée où elle a été déclarée. Cette « action à distance » peut produire du code difficile à suivre, car il n'est pas toujours évident de savoir où une variable a reçu une valeur.
On peut examiner la pile d'appels à l'aide de la variable tableau FUNCNAME.
Une fonction, comme n'importe quelle commande, possède un statut de sortie. Par défaut, le statut d'une fonction est le statut de sortie de la dernière commande exécutée.
On peut utiliser la commande return pour quitter une fonction avec un statut de sortie précis.
check_password () {
if [[ $1 == "secret" ]]; then
return 0
else
return 1
fi
}
read -sp "Enter your password: " pass
if check_password "$pass"; then
echo "Correct!"
else
echo "Wrong password."
fi
Utiliser return sans argument renvoie le statut de la dernière commande exécutée.
À noter que la fonction check_password peut se simplifier ainsi :
check_password () { [[ $1 == "secret" ]]; }
[[...]] a un statut de sortie : 0 pour « vrai », 1 pour « faux ».{...} doit comporter soit un retour à la ligne, soit un point-virgule avant l'accolade fermante.Le statut de retour d'une fonction n'est qu'un nombre. Comment une fonction peut-elle produire une sortie ?
Une fonction peut écrire sur la sortie standard. Utilise la substitution de commande que tu connais pour la récupérer :
d6 () { echo "$(( 1 + RANDOM % 6 ))"; }
die=$( d6 )
echo "You rolled a $die."
Le statut de sortie d'une fonction reste disponible même lorsque l'on capture sa sortie.
roll () {
local n=$1
if (( 4 <= n && n <= 20 )); then
echo "$(( 1 + RANDOM % n ))" # exit status is 0
else
return 1
fi
}
read -p "How many faces does your die have? " faces
if die=$( roll "$faces" ); then
echo "You rolled a $die."
else
echo "I can't roll a die with $faces faces."
fi
Les fonctions peuvent s'appeler elles-mêmes de manière récursive. Par défaut, la profondeur de récursion n'a pas de limite.
Un exemple :
fibonacci() {
local n=$1
if (( n <= 1 )); then
echo "1"
else
local a=$(fibonacci "$(( n - 1 ))")
local b=$(fibonacci "$(( n - 2 ))")
echo "$(( a + b ))"
fi
}
for i in {1..10}; do fibonacci "$i"; done
# => 1
# => 2
# => 3
# => 5
# => 8
# => 13
# => 21
# => 34
# => 55
# => 89
On peut contrôler la profondeur de récursion avec la variable FUNCNEST.
bash -c '
recur() {
echo $1
recur $(($1 + 1))
}
FUNCNEST=5
recur 1
'
1
2
3
4
5
environment: line 1: recur: maximum function nesting level exceeded (5)