Projektowanie funkcji z zakresem lokalnym i kodami zwrotnymi
Pisz funkcje korzystające ze zmiennych lokalnych, statusów zakończenia i wartości zwracanych przez printf zamiast zawodnych zmiennych globalnych.
Projektowanie funkcji z zakresem lokalnym i kodami zwrotnymi to bezpłatna lekcja Linux Command Line & Bash Scripting Mastery na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Linux Command Line & Bash Scripting Mastery, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Linux Command Line & Bash Scripting Mastery zawiera 4 lekcji w sumie.
Dlaczego zasięg funkcji ma znaczenie
W Bash zmienne są domyślnie globalne. Zmienna ustawiona wewnątrz funkcji przenika do zasięgu wywołującego, chyba że zostanie jawnie zadeklarowana jako local. Jest to częste źródło trudnych do wykrycia błędów w skryptach powłoki.
- Funkcje bez
localmogą po cichu nadpisywać zmienne wywołującego. - Zmienne
localsą usuwane po zakończeniu działania funkcji. - Wyraźne granice zasięgu sprawiają, że funkcje są wielokrotnego użytku i łatwe do testowania w izolacji.
W tej lekcji nauczy się Pan/Pani pisać samodzielne funkcje: używać zmiennych local, przekazywać wyniki za pomocą kodów wyjścia i printf oraz nigdy nie polegać na niejawnym stanie globalnym.
Problem wycieku do zakresu globalnego
Oto konkretny przykład wycieku zmiennej globalnej. Funkcja set_name ustawia zmienną o nazwie result, przez co po cichu nadpisuje własną zmienną result wywołującego.
Uruchom ten skrypt i obserwuj nieoczekiwany wynik — zmienna result wywołującego znika po wywołaniu funkcji.
#!/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'Deklarowanie zmiennych lokalnych za pomocą „local”
Wbudowane polecenie local ogranicza zakres zmiennej do otaczającej funkcji oraz wszystkich funkcji przez nią wywoływanych. Poza funkcją zmienna jest albo nieustawiona, albo zachowuje swoją wcześniejszą wartość.
local varname— deklaruje zmienną bez przypisywania wartości.local varname="value"— deklaruje zmienną i przypisuje jej wartość w jednym kroku.local -i count=0— deklaruje lokalną zmienną typu całkowitego.local -r PI=3.14159— deklaruje lokalną stałą tylko do odczytu.
Dobra praktyka: każdą zmienną wewnątrz funkcji należy deklarować jako local, chyba że celowo ma być globalna.
#!/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'Kody zakończenia jako wartości zwracane
Funkcje Bash nie mogą zwracać ciągów znaków za pomocą return — return ustawia wyłącznie całkowity status zakończenia (0–255). Przyjęta konwencja jest następująca:
return 0— powodzeniereturn 1(lub dowolna wartość różna od zera) — niepowodzenie
Wywołujący odczytuje status zakończenia za pomocą $? bezpośrednio po wywołaniu albo używa funkcji bezpośrednio jako warunku if. Kody zakończenia to idiomatyczny sposób sygnalizowania powodzenia lub niepowodzenia funkcji.
#!/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
donePrzekazywanie wyników tekstowych za pomocą printf
Gdy funkcja musi zwrócić wynik tekstowy, standardowy wzorzec polega na wypisaniu go na stdout i przechwyceniu za pomocą substytucji poleceń $(). Użycie printf zamiast echo jest preferowane, ponieważ:
printfdomyślnie nie dodaje końcowego znaku nowej linii (chyba że dołączą Państwo\n).- Zachowanie
printfjest spójne i zdefiniowane przez POSIX, natomiastechoróżni się w zależności od powłoki. - Substytucja poleceń usuwa końcowe znaki nowej linii, dlatego
printf '%s' "$value"zapewnia precyzyjny wynik.
#!/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"Łączenie kodów zakończenia z wyjściem stdout
Dobrze zaprojektowana funkcja może jednocześnie wypisać wynik (w przypadku powodzenia) i zasygnalizować niepowodzenie (za pomocą kodu zakończenia). Wywołujący na podstawie kodu zakończenia decyduje, co zrobić, zanim zaufa danym wyjściowym.
Poniższy wzorzec jest szeroko stosowany w rzeczywistych bibliotekach Bash:
- W przypadku powodzenia:
printfwypisuje wynik, a funkcja wykonujereturn 0. - W przypadku niepowodzenia: komunikat diagnostyczny jest zapisywany na stderr (nie na stdout), a funkcja wykonuje
return 1. - Zapisywanie błędów na stderr pozostawia stdout wolne do przekierowywania potokami.
#!/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)."
fiOchrona funkcji rekurencyjnych za pomocą „local”
Rekurencja jest jednym z najwyraźniejszych przykładów pokazujących, dlaczego local jest niezbędne. Każde rekurencyjne wywołanie otrzymuje własną, niezależną kopię każdej zmiennej local na stosie wywołań. Bez local każde wywołanie nadpisywałoby tę samą zmienną globalną i zwracało nieprawidłowe wyniki.
Poniższa funkcja silni jest bezpieczna, ponieważ zmienne n i sub są lokalne dla każdej ramki stosu.
#!/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)"
doneUnikanie pułapki podpowłoki za pomocą local -n (nameref)
Substytucja poleceń $() jest wykonywana w podpowłoce. Przypisania zmiennych wykonane wewnątrz niej są niewidoczne dla powłoki nadrzędnej. Gdy funkcja musi zapisać wartość do zmiennej przekazanej przez wywołującego, bez używania podpowłoki, należy użyć odwołania po nazwie (local -n), dostępnego w Bash 4.3 i nowszych.
local -n ref="$1"tworzy zrefalias zmiennej, której nazwa jest przechowywana w$1.- Przypisanie wartości do
refwewnątrz funkcji bezpośrednio zmienia zmienną wywołującego. - Pozwala to uniknąć podpowłoki, a jednocześnie zachować szczegóły implementacji w zakresie lokalnym.
#!/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]}"Tworzenie małej biblioteki funkcji
W rzeczywistych projektach Bash funkcje wielokrotnego użytku są umieszczane w plikach bibliotecznych, wczytywanych przez skrypty za pomocą source (lub operatora kropki .). Zasady dobrego projektowania bibliotek:
- Każda zmienna wewnątrz funkcji bibliotecznej musi być zadeklarowana jako
local. - Funkcje biblioteczne nigdy nie wykonują
exit— używająreturn, dzięki czemu wywołujący może działać dalej. - Należy stosować spójny prefiks przestrzeni nazw (np.
str_,log_), aby uniknąć kolizji nazw. - Należy zabezpieczyć się przed wielokrotnym wczytaniem za pomocą zmiennej sygnalizacyjnej.
Poniżej znajduje się minimalna biblioteka narzędzi do obsługi ciągów znaków, zgodna z tymi konwencjami.
#!/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
fiSprawdzanie argumentów wewnątrz funkcji
Funkcje otrzymujące argumenty powinny sprawdzać je na początku i zwracać określony kod zakończenia w przypadku nieprawidłowych danych wejściowych. Nazywa się to wzorcem klauzuli ochronnej — szybkie i jednoznaczne kończenie działania w razie błędu.
- Liczbę argumentów należy sprawdzać za pomocą
$#. - Typy lub formaty należy zweryfikować przed wykonaniem jakichkolwiek operacji.
- Komunikaty diagnostyczne należy wypisywać wyłącznie na stderr, nigdy na stdout.
- Należy używać różnych niezerowych kodów zwrotu (np. 1 = nieprawidłowe argumenty, 2 = nie znaleziono pliku), aby wywołujący mógł odmiennie reagować na różne przyczyny niepowodzenia.
#!/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: $?"
fiPołączenie wszystkiego: przykład z praktyki
Oto kompletny, samodzielny skrypt pokazujący współdziałanie wszystkich pojęć z tej lekcji:
- Zmienne
localw każdej funkcji. - Kody zakończenia do sygnalizowania powodzenia lub niepowodzenia.
printfdo przekazywania wyników tekstowych.- Błędy zapisywane na stderr, a wyniki na stdout.
- Klauzule ochronne do sprawdzania argumentów.
Prześledź przepływ działania: parse_version wyodrębnia dane, version_ge je porównuje, a main korzysta z obu funkcji w uporządkowany sposób.
#!/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
}
mainSprawdzenie wiedzy: zmienne lokalne i wartości zwracane
Rozważ następującą funkcję Bash. Jaki jest prawidłowy sposób przechwycenia jej wyniku tekstowego przez wywołującego i które stwierdzenie dotyczące zmiennej tmp jest prawdziwe?
transform() {
local tmp="${1,,}" # lowercase
printf '%s' "$tmp"
return 0
}Podsumowanie: funkcje z lokalnym zakresem i kodami zwrotu
W tej lekcji nauczyli się Państwo pisać funkcje Bash, które są przejrzyste, łatwe do łączenia i bezpieczne:
- Zawsze używaj
localdla zmiennych wewnątrz funkcji, aby nie zanieczyszczać zakresu wywołującego. - Używaj kodów zakończenia (
return 0/1/N) do sygnalizowania powodzenia lub niepowodzenia — naturalnie współpracują zif,&&i||. - Używaj
printfdo stdout, aby przekazywać wyniki tekstowe; przechwytuj je przez$()u wywołującego. - Zapisuj błędy na stderr (
>&2), aby stdout pozostał wolny na potrzeby przepływu danych i potoków. - Używaj
local -n(nameref), gdy trzeba zapisać wartość do zmiennej przekazanej przez wywołującego bez narzutu podpowłoki. - Klauzule ochronne (wczesne sprawdzanie argumentów i natychmiastowy zwrot w przypadku nieprawidłowych danych) zwiększają niezawodność funkcji i jasno dokumentują ich działanie.
- Pliki biblioteczne powinny być wczytywane za pomocą
source, używać prefiksów przestrzeni nazw, nigdy nie wywoływaćexiti chronić się przed wielokrotnym wczytaniem.
Opanowanie tych wzorców odróżnia kruche, jednorazowe skrypty od profesjonalnych i łatwych w utrzymaniu baz kodu Bash.
Często zadawane pytania
Czy lekcja „Projektowanie funkcji z zakresem lokalnym i kodami zwrotnymi” jest bezpłatna?
Tak — pełny tekst „Projektowanie funkcji z zakresem lokalnym i kodami zwrotnymi” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Linux Command Line & Bash Scripting Mastery, przejdź na CoddyKit PRO. Kurs Linux Command Line & Bash Scripting Mastery zawiera 4 lekcji w sumie.
Co nauczysz się w „Projektowanie funkcji z zakresem lokalnym i kodami zwrotnymi”?
Pisz funkcje korzystające ze zmiennych lokalnych, statusów zakończenia i wartości zwracanych przez printf zamiast zawodnych zmiennych globalnych. Ćwiczysz Linux Command Line & Bash Scripting Mastery z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Linux Command Line & Bash Scripting Mastery?
Nie wymagamy żadnego doświadczenia. Linux Command Line & Bash Scripting Mastery w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Projektowanie funkcji z zakresem lokalnym i kodami zwrotnymi”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Linux Command Line & Bash Scripting Mastery?
Tak. Każda lekcja Linux Command Line & Bash Scripting Mastery zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Projektowanie funkcji z zakresem lokalnym i kodami zwrotnymi
- Budowanie i dołączanie wielokrotnego użytku bibliotek Bash
- Analizowanie flag i argumentów za pomocą getopts
- Przekazywanie tablic i map asocjacyjnych między funkcjami