0Pricing
Linux Command Line & Bash Scripting Mastery · Leçon

Concevoir des fonctions avec une portée locale et des codes de retour

Écrivez des fonctions qui utilisent des variables locales, des statuts de sortie et des valeurs de retour fondées sur printf plutôt que des variables globales fragiles.

Concevoir des fonctions avec une portée locale et des codes de retour est une leçon Linux Command Line & Bash Scripting Mastery gratuite sur CoddyKit. Ceci est la leçon 1 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Linux Command Line & Bash Scripting Mastery, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Linux Command Line & Bash Scripting Mastery comprend 4 leçons au total.

Pourquoi la portée des fonctions est importante

Dans Bash, les variables sont globales par défaut. Une variable définie dans une fonction se propage dans la portée de l’appelant, sauf si vous la déclarez explicitement avec local. C’est une source courante de bogues subtils dans les scripts shell.

  • Les fonctions sans local peuvent écraser silencieusement les variables de l’appelant.
  • Les variables local sont détruites lorsque la fonction renvoie son résultat.
  • Des limites de portée claires rendent les fonctions réutilisables et testables isolément.

Cette leçon vous apprend à écrire des fonctions autonomes : elles utilisent des variables local, communiquent leurs résultats par des codes de sortie et printf, et ne dépendent jamais d’un état global implicite.

Le problème des fuites globales

Voici un exemple concret de fuite d'une variable globale. La fonction set_name définit une variable appelée result, ce qui écrase discrètement la propre variable result de l'appelant.

Exécutez ce script et observez la sortie inattendue : la variable result de l'appelant a disparu après l'appel de la fonction.

#!/usr/bin/env bash

set_name() {
    result="Alice"   # No 'local' — this is GLOBAL
}

result="important data"
echo "Before: $result"

set_name

echo "After:  $result"   # Prints 'Alice', not 'important data'

Déclarer des variables locales avec « local »

La commande intégrée local limite la portée d'une variable à la fonction englobante et aux fonctions qu'elle appelle. En dehors de la fonction, la variable n'est soit pas définie, soit conserve sa valeur précédente.

  • local varname — déclare la variable sans lui attribuer de valeur.
  • local varname="value" — déclare et attribue une valeur en une seule étape.
  • local -i count=0 — déclare une variable locale de type entier.
  • local -r PI=3.14159 — déclare une constante locale en lecture seule.

Bonne pratique : déclarez chaque variable à l'intérieur d'une fonction avec local, sauf si vous avez intentionnellement besoin qu'elle soit globale.

#!/usr/bin/env bash

greet() {
    local name="$1"          # local — safe
    local greeting="Hello, ${name}!"
    echo "$greeting"
}  # 'name' and 'greeting' vanish here

name="global value"
greet "Bob"
echo "name is still: $name"   # Prints 'global value'

Les codes de sortie comme valeurs de retour

Les fonctions Bash ne peuvent pas return de chaînes de caractères : return définit uniquement un statut de sortie entier (0–255). Par convention :

  • return 0 — réussite
  • return 1 (ou toute valeur non nulle) — échec

L'appelant lit le statut de sortie avec $? immédiatement après l'appel, ou utilise directement la fonction dans une condition if. Les codes de sortie sont la manière idiomatique de signaler la réussite ou l'échec d'une fonction.

#!/usr/bin/env bash

is_even() {
    local -i n="$1"
    (( n % 2 == 0 ))   # arithmetic command: exits 0 if true, 1 if false
}

for num in 2 3 7 10; do
    if is_even "$num"; then
        echo "$num is even"
    else
        echo "$num is odd"
    fi
done

Communiquer des résultats textuels avec printf

Lorsqu'une fonction doit renvoyer un résultat textuel, la méthode standard consiste à l'écrire sur la sortie standard, puis à le récupérer avec une substitution de commande $(). Il est préférable d'utiliser printf plutôt que echo, car :

  • printf n'ajoute pas de saut de ligne final par défaut (sauf si vous incluez \n).
  • Le comportement de printf est cohérent et défini par POSIX ; celui de echo varie selon les interpréteurs de commandes.
  • La substitution de commande supprime les sauts de ligne finaux ; printf '%s' "$value" est donc précis.
#!/usr/bin/env bash

to_uppercase() {
    local input="$1"
    printf '%s' "${input^^}"   # Bash 4+ parameter expansion
}

word="hello"
upper=$(to_uppercase "$word")
echo "Original: $word"
echo "Upper:    $upper"

Combiner les codes de sortie et la sortie standard

Une fonction bien conçue peut à la fois afficher un résultat (en cas de réussite) et signaler un échec (au moyen du code de sortie). L'appelant décide de la marche à suivre en fonction du code de sortie avant de faire confiance à la sortie.

Le modèle ci-dessous est largement utilisé dans les bibliothèques Bash réelles :

  • En cas de réussite : afficher le résultat avec printf et exécuter return 0.
  • En cas d'échec : écrire un diagnostic sur la sortie d'erreur standard (et non sur la sortie standard), puis exécuter return 1.
  • Écrire les erreurs sur la sortie d'erreur standard permet de conserver la sortie standard propre pour le chaînage.
#!/usr/bin/env bash

divide() {
    local -i numerator="$1"
    local -i denominator="$2"

    if (( denominator == 0 )); then
        printf 'Error: division by zero\n' >&2
        return 1
    fi

    printf '%d' $(( numerator / denominator ))
    return 0
}

if result=$(divide 20 4); then
    echo "20 / 4 = $result"
else
    echo "Division failed."
fi

if result=$(divide 10 0); then
    echo "10 / 0 = $result"
else
    echo "Division failed (caught the error)."
fi

Utiliser « local » pour protéger les fonctions récursives

La récursivité montre particulièrement clairement pourquoi local est indispensable. Chaque appel récursif reçoit sa propre copie indépendante de chaque variable local dans la pile d'appels. Sans local, chaque appel écraserait la même variable globale et produirait des résultats incorrects.

La fonction factorielle ci-dessous est sûre, car n et sub sont locaux à chaque cadre de pile.

#!/usr/bin/env bash

factorial() {
    local -i n="$1"
    local -i sub

    if (( n <= 1 )); then
        printf '1'
        return 0
    fi

    sub=$(factorial $(( n - 1 )))
    printf '%d' $(( n * sub ))
}

for i in 1 2 3 4 5 6; do
    echo "${i}! = $(factorial $i)"
done

Éviter le piège du sous-interpréteur avec local -n (référence de nom)

La substitution de commande $() s'exécute dans un sous-interpréteur. Toute attribution de variable effectuée à l'intérieur est invisible pour l'interpréteur parent. Lorsque vous devez permettre à une fonction d'écrire dans une variable fournie par l'appelant sans utiliser de sous-interpréteur, utilisez une référence de nom (local -n), disponible dans Bash 4.3 et versions ultérieures.

  • local -n ref="$1" fait de ref un alias de la variable dont le nom est stocké dans $1.
  • Attribuer une valeur à ref à l'intérieur de la fonction modifie directement la variable de l'appelant.
  • Cela évite un sous-interpréteur tout en conservant les détails d'implémentation dans une portée locale.
#!/usr/bin/env bash

# Fills caller's array by reference — no subshell needed
read_csv_line() {
    local -n _out="$1"    # nameref to caller's variable
    local line="$2"
    local IFS=','
    read -ra _out <<< "$line"
}

declare -a fields
read_csv_line fields "alice,30,engineer"

echo "Name:  ${fields[0]}"
echo "Age:   ${fields[1]}"
echo "Role:  ${fields[2]}"

Créer une petite bibliothèque de fonctions

Dans les projets Bash réels, les fonctions réutilisables sont réparties dans des fichiers de bibliothèque chargés par les scripts avec source (ou l'opérateur point .). Règles de conception d'une bonne bibliothèque :

  • Toute variable à l'intérieur d'une fonction de bibliothèque doit être local.
  • Les fonctions de bibliothèque n'exécutent jamais exit : elles exécutent return, afin que l'appelant reste actif.
  • Utilisez un préfixe d'espace de noms cohérent (par exemple str_, log_) pour éviter les collisions de noms.
  • Protégez-vous contre les chargements multiples à l'aide d'une variable sentinelle.

Vous trouverez ci-dessous une bibliothèque minimale d'utilitaires pour les chaînes, qui respecte ces conventions.

#!/usr/bin/env bash
# lib/str.sh  — string utility library

[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1

str_trim() {
    local str="$1"
    str="${str#"${str%%[![:space:]]*}"}"
    str="${str%"${str##*[![:space:]]}"}" 
    printf '%s' "$str"
}

str_repeat() {
    local -i times="$2"
    local char="$1"
    local -i i
    for (( i = 0; i < times; i++ )); do
        printf '%s' "$char"
    done
}

str_contains() {
    local haystack="$1"
    local needle="$2"
    [[ "$haystack" == *"$needle"* ]]
}

# --- self-test when executed directly ---
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
    trimmed=$(str_trim "   hello world   ")
    echo "Trimmed: '${trimmed}'"
    str_repeat '-' 20; echo
    if str_contains "bash scripting" "script"; then
        echo "Contains: yes"
    fi
fi

Valider les arguments à l'intérieur des fonctions

Les fonctions qui reçoivent des arguments doivent les valider rapidement et renvoyer un code de sortie précis en cas d'entrée incorrecte. C'est le modèle de la clause de garde : échouer rapidement et clairement.

  • Vérifiez le nombre d'arguments avec $#.
  • Validez les types ou les formats avant d'effectuer le moindre travail.
  • Affichez les messages de diagnostic uniquement sur la sortie d'erreur standard, jamais sur la sortie standard.
  • Utilisez des codes de retour non nuls distincts (par exemple, 1 = arguments incorrects, 2 = fichier introuvable) afin que les appelants puissent réagir différemment selon le mode d'échec.
#!/usr/bin/env bash

file_line_count() {
    if (( $# != 1 )); then
        printf 'Usage: file_line_count <file>\n' >&2
        return 1
    fi

    local file="$1"

    if [[ ! -f "$file" ]]; then
        printf 'Error: not a file: %s\n' "$file" >&2
        return 2
    fi

    if [[ ! -r "$file" ]]; then
        printf 'Error: cannot read: %s\n' "$file" >&2
        return 3
    fi

    local -i count
    count=$(wc -l < "$file")
    printf '%d' "$count"
    return 0
}

# Test with /etc/hosts (exists on every Linux/macOS system)
if lines=$(file_line_count /etc/hosts); then
    echo "/etc/hosts has $lines lines"
else
    echo "Failed with exit code: $?"
fi

Tout assembler : un exemple concret

Voici un script complet et autonome qui montre tous les concepts de cette leçon utilisés ensemble :

  • Des variables local dans chaque fonction.
  • Des codes de sortie pour signaler la réussite ou l'échec.
  • printf pour communiquer des résultats textuels.
  • Les erreurs écrites sur la sortie d'erreur standard et les résultats sur la sortie standard.
  • Des clauses de garde pour valider les arguments.

Étudiez le déroulement : parse_version extrait les données, version_ge les compare et main utilise les deux proprement.

#!/usr/bin/env bash

# Parse a semver string into components via nameref
parse_version() {
    local -n _major="$2" _minor="$3" _patch="$4"
    local version="$1"
    local IFS='.'
    local -a parts
    read -ra parts <<< "$version"
    _major="${parts[0]:-0}"
    _minor="${parts[1]:-0}"
    _patch="${parts[2]:-0}"
}

# Return 0 if version $1 >= version $2
version_ge() {
    local -i maj_a min_a pat_a
    local -i maj_b min_b pat_b
    parse_version "$1" maj_a min_a pat_a
    parse_version "$2" maj_b min_b pat_b

    if   (( maj_a != maj_b )); then (( maj_a > maj_b ))
    elif (( min_a != min_b )); then (( min_a > min_b ))
    else                             (( pat_a >= pat_b ))
    fi
}

require_bash_version() {
    local required="$1"
    local actual="${BASH_VERSION%%(*}"
    if version_ge "$actual" "$required"; then
        printf 'Bash %s satisfies >= %s\n' "$actual" "$required"
        return 0
    else
        printf 'Error: need Bash >= %s, got %s\n' "$required" "$actual" >&2
        return 1
    fi
}

main() {
    require_bash_version "4.3" || return 1
    require_bash_version "99.0" || true   # demonstrates failure path
}

main

Vérification des connaissances : variables locales et valeurs de retour

Considérez la fonction Bash suivante. Quelle est la bonne manière de récupérer son résultat textuel dans l'appelant, et quelle affirmation concernant la variable tmp est vraie ?

transform() {
    local tmp="${1,,}"   # lowercase
    printf '%s' "$tmp"
    return 0
}

Récapitulatif : fonctions à portée locale et codes de retour

Dans cette leçon, vous avez appris à écrire des fonctions Bash propres, composables et sûres :

  • Utilisez toujours local pour les variables à l'intérieur des fonctions afin d'éviter de polluer la portée de l'appelant.
  • Utilisez les codes de sortie (return 0/1/N) pour signaler la réussite ou l'échec : ils s'intègrent naturellement à if, && et ||.
  • Utilisez printf vers la sortie standard pour communiquer les résultats textuels ; récupérez-les avec $() dans l'appelant.
  • Écrivez les erreurs sur la sortie d'erreur standard (>&2) afin de garder la sortie standard propre pour le flux de données et le chaînage.
  • Utilisez local -n (référence de nom) lorsque vous devez écrire dans une variable fournie par l'appelant sans le coût d'un sous-interpréteur.
  • Les clauses de garde (valider rapidement les arguments et retourner immédiatement en cas d'entrée incorrecte) rendent les fonctions robustes et explicites.
  • Les fichiers de bibliothèque doivent être chargés, utiliser des préfixes d'espace de noms, ne jamais appeler exit et se protéger contre les chargements multiples.

Maîtriser ces modèles fait la différence entre des scripts ponctuels fragiles et des bases de code Bash professionnelles et faciles à maintenir.

Questions Fréquemment Posées

La leçon « Concevoir des fonctions avec une portée locale et des codes de retour » est-elle gratuite ?

Oui — le texte complet de « Concevoir des fonctions avec une portée locale et des codes de retour » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Linux Command Line & Bash Scripting Mastery, passe à CoddyKit PRO. Le cours Linux Command Line & Bash Scripting Mastery comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Concevoir des fonctions avec une portée locale et des codes de retour » ?

Écrivez des fonctions qui utilisent des variables locales, des statuts de sortie et des valeurs de retour fondées sur printf plutôt que des variables globales fragiles. Tu pratiques Linux Command Line & Bash Scripting Mastery avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Linux Command Line & Bash Scripting Mastery ?

Aucune expérience préalable n'est requise. Linux Command Line & Bash Scripting Mastery sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 1 sur 4.

Combien de temps prend la leçon « Concevoir des fonctions avec une portée locale et des codes de retour » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Linux Command Line & Bash Scripting Mastery ?

Oui. Chaque leçon Linux Command Line & Bash Scripting Mastery inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Concevoir des fonctions avec une portée locale et des codes de retour
  2. Créer et charger des bibliothèques Bash réutilisables
  3. Analyser les options et les arguments avec getopts
  4. Transmettre des tableaux et des cartes associatives entre fonctions
← Retour à Linux Command Line & Bash Scripting Mastery