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 DevOps Bootcamp 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 DevOps Bootcamp, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours DevOps Bootcamp 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
localpeuvent écraser silencieusement les variables de l’appelant. - Les variables
localsont 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éussitereturn 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
doneCommuniquer 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 :
printfn'ajoute pas de saut de ligne final par défaut (sauf si vous incluez\n).- Le comportement de
printfest cohérent et défini par POSIX ; celui deechovarie 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
printfet exécuterreturn 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)."
fiUtiliser « 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 derefun 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écutentreturn, 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
fiValider 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: $?"
fiTout assembler : un exemple concret
Voici un script complet et autonome qui montre tous les concepts de cette leçon utilisés ensemble :
- Des variables
localdans chaque fonction. - Des codes de sortie pour signaler la réussite ou l'échec.
printfpour 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
}
mainVé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
localpour 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
printfvers 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
exitet 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 DevOps Bootcamp, passe à CoddyKit PRO. Le cours DevOps Bootcamp 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 DevOps Bootcamp 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 DevOps Bootcamp ?
Aucune expérience préalable n'est requise. DevOps Bootcamp 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 DevOps Bootcamp ?
Oui. Chaque leçon DevOps Bootcamp 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
- Concevoir des fonctions avec une portée locale et des codes de retour
- Créer et charger des bibliothèques Bash réutilisables
- Analyser les options et les arguments avec getopts
- Transmettre des tableaux et des cartes associatives entre fonctions