Functies ontwerpen met lokale scope en returncodes
Schrijf functies die lokale variabelen, exitstatussen en op printf gebaseerde returnwaarden gebruiken in plaats van kwetsbare globals.
Functies ontwerpen met lokale scope en returncodes is een gratis DevOps-bootcamp-les op CoddyKit. Dit is les 1 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject DevOps-bootcamp. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus DevOps-bootcamp bevat in totaal 4 lessen.
Waarom de reikwijdte van functies belangrijk is
In Bash zijn variabelen standaard globaal. Een variabele die je binnen een functie instelt, lekt naar de reikwijdte van de aanroeper, tenzij je deze expliciet als local declareert. Dit is een veelvoorkomende bron van subtiele fouten in shellscripts.
- Functies zonder
localkunnen variabelen van de aanroeper stilletjes overschrijven. local-variabelen worden verwijderd wanneer de functie terugkeert.- Duidelijke grenzen van de reikwijdte maken functies herbruikbaar en testbaar in isolatie.
In deze les leer je zelfvoorzienende functies schrijven: ze gebruiken local-variabelen, geven resultaten door via afsluitcodes en printf en vertrouwen nooit op impliciete globale toestand.
Het probleem van globale lekken
Hier is een concreet voorbeeld van een lek van een globale variabele. De functie set_name stelt een variabele met de naam result in, waarmee de eigen result-variabele van de aanroeper ongemerkt wordt overschreven.
Voer dit script uit en bekijk de onverwachte uitvoer — de result van de aanroeper is verdwenen nadat de functie is aangeroepen.
#!/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'Lokale variabelen declareren met 'local'
De ingebouwde opdracht local beperkt de scope van een variabele tot de omringende functie en alle functies die deze aanroept. Buiten de functie is de variabele niet ingesteld of behoudt deze de vorige waarde.
local varname— declareert zonder een waarde toe te kennen.local varname="value"— declareert de variabele en kent er in één stap een waarde aan toe.local -i count=0— declareert een lokale variabele met een integer-type.local -r PI=3.14159— declareert een alleen-lezenconstante.
Best practice: declareer elke variabele binnen een functie als local, tenzij je deze bewust globaal nodig hebt.
#!/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'Exitcodes als retourwaarden
Bash-functies kunnen geen tekenreeksen returnen — return stelt alleen een gehele exitstatus in (0–255). Volgens de conventie geldt:
return 0— geslaagdreturn 1(of een andere waarde die niet nul is) — mislukt
De aanroeper leest de exitstatus via $? direct na de aanroep, of gebruikt de functie rechtstreeks in een if-voorwaarde. Exitcodes zijn de gebruikelijke manier om succes of mislukking van een functie door te geven.
#!/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
doneTekenreeksresultaten doorgeven via printf
Wanneer een functie een tekenreeksresultaat moet retourneren, is het gebruikelijke patroon om naar stdout te schrijven en de uitvoer vast te leggen met opdrachtvervanging $(). printf heeft de voorkeur boven echo, omdat:
printfstandaard geen afsluitende nieuwe regel toevoegt (tenzij je\nopneemt).- het gedrag van
printfconsistent is en door POSIX wordt vastgelegd;echoverschilt per shell. - opdrachtvervanging afsluitende nieuwe regels verwijdert, waardoor
printf '%s' "$value"precies werkt.
#!/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"Exitcodes en stdout-uitvoer combineren
Een goed ontworpen functie kan tegelijk een resultaat afdrukken (bij succes) en een mislukking signaleren (via de exitcode). De aanroeper bepaalt op basis van de exitcode wat er moet gebeuren, voordat de uitvoer wordt vertrouwd.
Het onderstaande patroon wordt veel gebruikt in echte Bash-bibliotheken:
- Bij succes: gebruik
printfvoor het resultaat enreturn 0. - Bij mislukking: schrijf een diagnose naar stderr (niet naar stdout) en gebruik
return 1. - Door fouten naar stderr te schrijven, blijft stdout schoon voor doorsturen via een pijplijn.
#!/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'local' gebruiken om recursieve functies te beschermen
Recursie laat als een van de duidelijkste voorbeelden zien waarom local essentieel is. Elke recursieve aanroep krijgt een eigen, onafhankelijke kopie van elke local-variabele op de aanroepstack. Zonder local zou elke aanroep dezelfde globale variabele overschrijven en onjuiste resultaten opleveren.
De onderstaande faculteitsfunctie is veilig, omdat n en sub lokaal zijn voor elk stackframe.
#!/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)"
doneDe subshell-valkuil vermijden met local -n (nameref)
Opdrachtvervanging $() wordt uitgevoerd in een subshell. Variabeletoewijzingen daarin zijn niet zichtbaar in de bovenliggende shell. Wanneer je wilt dat een functie naar een door de aanroeper aangeleverde variabele schrijft zonder een subshell te gebruiken, gebruik je een nameref (local -n), beschikbaar in Bash 4.3 en hoger.
local -n ref="$1"maakt vanrefeen alias voor de variabele waarvan de naam in$1staat.- Een toewijzing aan
refbinnen de functie wijzigt de variabele van de aanroeper rechtstreeks. - Zo vermijd je een subshell, terwijl de implementatiedetails lokaal blijven.
#!/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]}"Een kleine functiebibliotheek bouwen
Echte Bash-projecten verdelen herbruikbare functies over bibliotheekbestanden die door scripts worden ingeladen met source (of de puntoperator .). Goede regels voor bibliotheekontwerp:
- Elke variabele binnen een bibliotheekfunctie moet
localzijn. - Bibliotheekfuncties gebruiken nooit
exit— ze gebruikenreturn, zodat de aanroeper actief blijft. - Gebruik een consistent naamruimtevoorvoegsel (bijvoorbeeld
str_,log_) om naamconflicten te voorkomen. - Voorkom dubbel inladen met een wachtvariabele.
Hieronder staat een minimale bibliotheek voor tekenreeksfuncties die deze conventies volgt.
#!/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
fiArgumenten binnen functies valideren
Functies die argumenten ontvangen, moeten deze vroegtijdig valideren en bij ongeldige invoer een specifieke exitcode retourneren. Dit heet het patroon van de beveiligingsclausule — faal snel en duidelijk.
- Controleer het aantal argumenten met
$#. - Valideer typen of indelingen voordat je werk uitvoert.
- Druk diagnoseberichten uitsluitend af naar stderr, nooit naar stdout.
- Gebruik verschillende exitcodes die niet nul zijn (bijvoorbeeld 1 = onjuiste argumenten, 2 = bestand niet gevonden), zodat aanroepers verschillend kunnen reageren op verschillende manieren waarop iets kan mislukken.
#!/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: $?"
fiAlles samenbrengen: een voorbeeld uit de praktijk
Hier is een compleet, zelfstandig script dat alle concepten uit deze les samenwerkend demonstreert:
local-variabelen in elke functie.- Exitcodes om succes of mislukking te signaleren.
printfom tekenreeksresultaten door te geven.- Fouten naar stderr en resultaten naar stdout schrijven.
- Beveiligingsclausules voor argumentvalidatie.
Bestudeer de uitvoeringsstroom: parse_version haalt gegevens op, version_ge vergelijkt deze en main gebruikt beide op een nette manier.
#!/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
}
mainKennistoets: lokale variabelen en retourwaarden
Bekijk de volgende Bash-functie. Wat is de juiste manier om het tekenreeksresultaat ervan in de aanroeper vast te leggen, en welke uitspraak over de variabele tmp is waar?
transform() {
local tmp="${1,,}" # lowercase
printf '%s' "$tmp"
return 0
}Samenvatting: functies met lokale scope en retourcodes
In deze les heb je geleerd hoe je schone, combineerbare en veilige Bash-functies schrijft:
- Gebruik altijd
localvoor variabelen binnen functies om te voorkomen dat je de scope van de aanroeper vervuilt. - Gebruik exitcodes (
return 0/1/N) om succes of mislukking te signaleren — ze werken vanzelfsprekend samen metif,&&en||. - Gebruik
printfnaar stdout om tekenreeksresultaten door te geven; leg ze in de aanroeper vast met$(). - Schrijf fouten naar stderr (
>&2), zodat stdout schoon blijft voor gegevensstromen en doorsturen via een pijplijn. - Gebruik
local -n(nameref) wanneer je naar een door de aanroeper aangeleverde variabele moet schrijven zonder de overhead van een subshell. - Beveiligingsclausules (argumenten vroeg valideren en bij ongeldige invoer meteen retourneren) maken functies robuust en verklaren hun werking duidelijk.
- Bibliotheekbestanden moeten worden ingeladen, naamruimtevoorvoegsels gebruiken, nooit
exitaanroepen en dubbel inladen voorkomen.
Het beheersen van deze patronen maakt het verschil tussen kwetsbare eenmalige scripts en professionele, onderhoudbare Bash-codebases.
Leer DevOps-bootcamp met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 142
- Lessen
- 568
Veelgestelde vragen
Is de les “Functies ontwerpen met lokale scope en returncodes” gratis?
Ja — je kunt hier op het web alle 3 lessen van het leerpad DevOps-bootcamp, waaronder “Functies ontwerpen met lokale scope en returncodes”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus DevOps-bootcamp bevat in totaal 4 lessen.
Wat leer ik in “Functies ontwerpen met lokale scope en returncodes”?
Schrijf functies die lokale variabelen, exitstatussen en op printf gebaseerde returnwaarden gebruiken in plaats van kwetsbare globals. Je oefent met DevOps-bootcamp door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met DevOps-bootcamp te beginnen?
Ervaring vooraf is niet nodig. DevOps-bootcamp op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 1 van 4.
Hoe lang duurt de les “Functies ontwerpen met lokale scope en returncodes”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over DevOps-bootcamp?
Ja. Elke les over DevOps-bootcamp bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- Functies ontwerpen met lokale scope en returncodes
- Herbruikbare Bash-bibliotheken bouwen en sourcen
- Flags en argumenten parsen met getopts
- Arrays en associatieve maps tussen functies doorgeven