Fo

Fonctions en Bash

{one: "%{count} exercice", many: "%{count} d'exercices", other: "%{count} exercices"}

À propos de Fonctions

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.

Définir une fonction

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.

Les paramètres d'une fonction

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

Advanced

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.

Les variables

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

Advanced

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.

Advanced

On peut examiner la pile d'appels à l'aide de la variable tableau FUNCNAME.

Les valeurs de retour

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.

Note

À noter que la fonction check_password peut se simplifier ainsi :

check_password () { [[ $1 == "secret" ]]; }
  1. La structure conditionnelle [[...]] a un statut de sortie : 0 pour « vrai », 1 pour « faux ».
  2. La structure de groupement {...} doit comporter soit un retour à la ligne, soit un point-virgule avant l'accolade fermante.

La sortie d'une fonction

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."

Utiliser à la fois la sortie et le statut

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

La récursion

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
Advanced

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)
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet