0Pricing
Linux Command Line & Bash Scripting Mastery · Lekcja

Przekazywanie tablic i map asocjacyjnych między funkcjami

Używaj namerefów i rozwijania pośredniego, aby bezpiecznie przekazywać złożone struktury danych do funkcji i z nich je odbierać.

Przekazywanie tablic i map asocjacyjnych między funkcjami to bezpłatna lekcja Linux Command Line & Bash Scripting Mastery na CoddyKit. To lekcja 4 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 przekazywanie tablic do funkcji jest trudne

W Bashu tablice i tablice asocjacyjne nie są wartościami pierwszej klasy. Przy próbie przekazania tablicy do funkcji nie można po prostu użyć $my_array — spowoduje to rozwinięcie tylko pierwszego elementu.

Rozważmy tę niepoprawną próbę:

my_func "${my_array[@]}"

Funkcja otrzymuje płaską listę słów. Nie ma możliwości ustalenia, gdzie kończy się jeden element, a zaczyna następny, i całkowicie traci strukturę tablicy.

Bash udostępnia dwa niezawodne mechanizmy przekazywania złożonych struktur danych:

  • Namerefs (declare -n) — odwołanie do innej zmiennej za pomocą jej nazwy (Bash 4.3+)
  • Rozwinięcie pośrednie (${!varname[@]}) — dostęp do zmiennej, której nazwa jest przechowywana w innej zmiennej (starsze wersje Basha)

W tej lekcji omówiono obie techniki, aby mogli Państwo pisać solidne, wielokrotnego użytku funkcje obsługujące tablice i mapy.

Przekazywanie tablicy przez nazwę za pomocą namerefu

Najbardziej przejrzystym nowoczesnym podejściem jest nameref: przekazują Państwo nazwę tablicy jako ciąg znaków, a wewnątrz funkcji deklarują lokalną zmienną będącą do niej odwołaniem.

Podstawowa składnia:

  • declare -n ref="$1" — ref staje się aliasem dowolnej zmiennej, której nazwa została przekazana jako pierwszy argument
  • Zmienną ref odczytuje się, iteruje po niej i modyfikuje ją dokładnie tak, jak oryginalną tablicę

Pozwala to uniknąć kopiowania całej tablicy i umożliwia funkcji pracę z tablicami o dowolnym rozmiarze.

#!/usr/bin/env bash

print_array() {
    declare -n arr="$1"   # nameref: arr -> caller's variable
    echo "Array has ${#arr[@]} elements:"
    for elem in "${arr[@]}"; do
        echo "  - $elem"
    done
}

fruits=(apple banana cherry)
print_array fruits

Modyfikowanie tablicy wywołującego wewnątrz funkcji

Ponieważ nameref jest prawdziwym aliasem, zapisy wykonywane za jego pośrednictwem zmieniają oryginalną zmienną wywołującego. Nie ma potrzeby zwracania wartości za pomocą echo ani używania zmiennych globalnych.

Dzięki temu namerefy doskonale nadają się do funkcji, które przekształcają tablice w miejscu — na przykład normalizują ciągi znaków, filtrują elementy lub sortują tablice.

#!/usr/bin/env bash

to_uppercase() {
    declare -n _arr="$1"
    for i in "${!_arr[@]}"; do
        _arr[$i]="${_arr[$i]^^}"   # ^^ = uppercase in Bash 4+
    done
}

words=(hello world bash)
echo "Before: ${words[@]}"
to_uppercase words
echo "After:  ${words[@]}"

Zwracanie nowej tablicy za pomocą namerefu

Funkcja może również zapisywać do tablicy dostarczonej przez wywołującego, przyjmując jej nazwę jako parametr wyjściowy. Wywołujący tworzy pustą tablicę, przekazuje jej nazwę, a funkcja ją wypełnia.

Ten wzorzec przypomina klasyczną konstrukcję z języka C, w której do funkcji przekazuje się wskaźnik do bufora wynikowego — z tą różnicą, że tutaj wszystko odbywa się w powłoce.

#!/usr/bin/env bash

get_even_numbers() {
    local -a source=("$@")
    # Last argument is the output array name — pop it off
    local out_name="${source[-1]}"
    unset 'source[-1]'

    declare -n _out="$out_name"
    _out=()   # clear
    for n in "${source[@]}"; do
        (( n % 2 == 0 )) && _out+=("$n")
    done
}

declare -a evens
get_even_numbers 1 2 3 4 5 6 7 8 evens
echo "Evens: ${evens[@]}"

Pułapka kolizji nazw namerefów

Namerefy mają jedną krytyczną pułapkę: jeśli lokalna zmienna nameref ma taką samą nazwę jak zmienna wywołującego, Bash wchodzi w nieskończoną pętlę samozwrotnego odwołania i skrypt kończy działanie błędem.

Jeśli na przykład wywołujący nazwał swoją tablicę arr, a funkcja wykonuje declare -n arr="$1", nameref arr wskazuje sam na siebie.

Konwencja zapobiegająca temu problemowi: należy poprzedzać wewnętrzne zmienne nameref podkreśleniem (np. _arr, _ref, _out). Wywołujący niemal nigdy nie używają takich nazw, więc kolizje stają się niezwykle rzadkie.

#!/usr/bin/env bash

# DANGER: caller uses variable named 'arr'
arr=(one two three)

bad_func() {
    declare -n arr="$1"   # collision! arr refers to itself
    echo "${arr[@]}"
}

good_func() {
    declare -n _arr="$1"  # underscore prefix avoids collision
    echo "${_arr[@]}"
}

# bad_func arr   # would hang / error
good_func arr

Przekazywanie tablic asocjacyjnych (map) przez nazwę

Tablice asocjacyjne (declare -A) działają z namerefami tak samo. Należy przekazać nazwę mapy jako ciąg znaków i zadeklarować nameref wewnątrz funkcji — otrzymują Państwo pełny dostęp do odczytu i zapisu wszystkich par klucz-wartość.

Jest to niezwykle przydatne w przypadku map konfiguracji, parserów opcji i tablic wyszukiwania, które muszą być współdzielone przez funkcje biblioteczne.

#!/usr/bin/env bash

print_map() {
    declare -n _map="$1"
    echo "Map contents:"
    for key in "${!_map[@]}"; do
        printf '  %-15s => %s\n' "$key" "${_map[$key]}"
    done
}

declare -A config
config[host]="localhost"
config[port]="5432"
config[db]="myapp"

print_map config

Scalanie dwóch map za pomocą namerefu

Ponieważ namerefy zapewniają funkcji aktywny alias, można w przejrzysty sposób implementować narzędzia takie jak scalanie map. Należy przekazać nazwy mapy docelowej oraz co najmniej jednej mapy źródłowej; funkcja iteruje po kluczach źródłowych i zapisuje je w mapie docelowej.

Namerefy umożliwiają napisanie jednej uniwersalnej funkcji merge_map, która działa z dowolnymi tablicami asocjacyjnymi w skrypcie.

#!/usr/bin/env bash

merge_map() {
    # merge_map dest src
    declare -n _dest="$1"
    declare -n _src="$2"
    for key in "${!_src[@]}"; do
        _dest["$key"]="${_src[$key]}"
    done
}

declare -A defaults=([timeout]=30 [retries]=3 [verbose]=false)
declare -A overrides=([retries]=5 [log_level]=debug)

merge_map defaults overrides

for k in "${!defaults[@]}"; do
    echo "$k = ${defaults[$k]}"
done

Rozwinięcie pośrednie: zgodność ze starszymi wersjami Basha

Jeśli muszą Państwo obsługiwać Bash 4.0–4.2 (lub starsze domyślne powłoki systemu macOS), declare -n jest niedostępne. Alternatywą jest rozwinięcie pośrednie za pomocą ${!varname}.

Rozwinięcie pośrednie interpretuje wartość varname jako nazwę zmiennej i zwraca wartość tej zmiennej. W przypadku tablic łączy się je ze specjalną składnią, aby uzyskać wszystkie elementy.

Ograniczenia w porównaniu z namerefami:

  • W praktyce działa tylko do odczytu (zapis wymaga użycia eval, co wiąże się z ryzykiem wstrzyknięcia kodu)
  • Nie można bezpiecznie iterować po kluczach tablic asocjacyjnych bez użycia eval
  • W miarę możliwości należy używać namerefów; po rozwinięcie pośrednie sięgać tylko wtedy, gdy jest to konieczne
#!/usr/bin/env bash

sum_array() {
    local arr_name="$1"
    # Indirect: build a temporary copy by expanding arr_name[@]
    local -a items
    eval "items=( \"\${${arr_name}[@]}\" )"
    local total=0
    for n in "${items[@]}"; do
        (( total += n ))
    done
    echo "$total"
}

numbers=(10 20 30 40)
result=$(sum_array numbers)
echo "Sum: $result"

Budowanie modułowej biblioteki skryptów

W profesjonalnych projektach Bash funkcje narzędziowe przechowuje się w plikach bibliotecznych (np. lib/array_utils.sh), a następnie dołącza w skryptach, które ich potrzebują.

Wzorzec wygląda następująco:

  • Każdy plik biblioteczny definiuje wyłącznie funkcje — nie zawiera efektów ubocznych na poziomie głównym
  • Należy zapobiegać wielokrotnemu dołączaniu za pomocą zmiennej sygnalizacyjnej: [[ -n $__LIB_LOADED ]] && return; __LIB_LOADED=1
  • Skrypty korzystające z biblioteki używają source lub ., aby ją załadować
  • Funkcje wymieniają złożone dane za pomocą namerefów, zachowując całą logikę w jednym miejscu

W rezultacie powstają skrypty, które można testować, ponownie wykorzystywać i łatwo utrzymywać — a nie jednorazowe sztuczki powłoki.

Przykład praktyczny: parser opcji zwracający mapę

Praktyczny przypadek użycia: parser opcji wiersza poleceń, który wypełnia tablicę asocjacyjną dostarczoną przez wywołującego. Wywołujący przekazuje nazwę tablicy jako pierwszy argument, a następnie rzeczywiste argumenty "$@".

Ten wzorzec pozwala dowolnemu skryptowi ponownie używać tego samego parsera bez kopiowania kodu i polegania na zmiennych globalnych.

#!/usr/bin/env bash

parse_opts() {
    declare -n _opts="$1"
    shift
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --host)   _opts[host]="$2";   shift 2 ;;
            --port)   _opts[port]="$2";   shift 2 ;;
            --user)   _opts[user]="$2";   shift 2 ;;
            *)        echo "Unknown option: $1" >&2; shift ;;
        esac
    done
}

declare -A opts=([host]=localhost [port]=5432 [user]=admin)
parse_opts opts --host db.example.com --port 3306

echo "Connecting to ${opts[host]}:${opts[port]} as ${opts[user]}"

Łączenie namerefów wejściowych i wyjściowych w jednej funkcji

Funkcje mogą jednocześnie przechowywać wiele namerefów — jeden dla danych wejściowych, drugi dla wyjściowych. Umożliwia to tworzenie funkcji wykonujących czyste przekształcenia: odczytują one strukturę źródłową, obliczają wynik i zapisują go w strukturze docelowej, nie modyfikując przy tym stanu globalnego.

Używanie różnych nazw dla poszczególnych namerefów, poprzedzonych podkreśleniem, eliminuje kolizje nawet wtedy, gdy zmienne wywołującego mają proste nazwy.

#!/usr/bin/env bash

filter_map_by_key_prefix() {
    # Usage: filter_map_by_key_prefix src_map dest_map prefix
    declare -n _src="$1"
    declare -n _dst="$2"
    local prefix="$3"

    _dst=()
    for key in "${!_src[@]}"; do
        if [[ $key == ${prefix}* ]]; then
            _dst["$key"]="${_src[$key]}"
        fi
    done
}

declare -A settings=(
    [db_host]=localhost [db_port]=5432
    [cache_ttl]=300     [cache_driver]=redis
    [app_debug]=true
)
declare -A db_settings

filter_map_by_key_prefix settings db_settings db_

for k in "${!db_settings[@]}"; do
    echo "$k = ${db_settings[$k]}"
done

Sprawdzenie wiedzy: namerefy i przekazywanie tablic

Sprawdź swoją wiedzę na temat przekazywania tablic i map między funkcjami w Bashu.

Podsumowanie: przekazywanie złożonych struktur danych w Bashu

Oto czego nauczyli się Państwo w tej lekcji:

  • Tablic nie można przekazywać przez wartość — zamiast tego należy przekazać nazwę zmiennej jako ciąg znaków.
  • declare -n ref="$varname" tworzy nameref: aktywny alias zmiennej wywołującego. Zarówno odczyt, jak i zapis są wykonywane na oryginalnej zmiennej. Wymaga Basha 4.3+.
  • Kolizja namerefów występuje, gdy zmienna wewnętrzna i zewnętrzna mają tę samą nazwę. Należy jej zapobiegać, poprzedzając wewnętrzne namerefy podkreśleniem (np. _arr, _map).
  • Parametry wyjściowe działają przez przekazanie nazwy pustej tablicy lub mapy wynikowej; funkcja wypełnia ją za pośrednictwem namerefu.
  • Wiele namerefów w jednej funkcji umożliwia wykonywanie czystych przekształceń danych wejściowych w wyjściowe bez globalnych efektów ubocznych.
  • Rozwinięcie pośrednie (${!varname[@]} + eval) to starsza metoda zastępcza dla Basha < 4.3, która wiąże się jednak z ryzykiem wstrzyknięcia kodu i w praktyce działa tylko do odczytu.
  • Połącz te techniki z plikami bibliotek dołączanymi za pomocą source, aby tworzyć modułowe, wielokrotnego użytku i łatwe do testowania bazy kodu Bash.

Często zadawane pytania

Czy lekcja „Przekazywanie tablic i map asocjacyjnych między funkcjami” jest bezpłatna?

Tak — pełny tekst „Przekazywanie tablic i map asocjacyjnych między funkcjami” 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 „Przekazywanie tablic i map asocjacyjnych między funkcjami”?

Używaj namerefów i rozwijania pośredniego, aby bezpiecznie przekazywać złożone struktury danych do funkcji i z nich je odbierać. Ć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 4 z 4.

Ile czasu zajmuje lekcja „Przekazywanie tablic i map asocjacyjnych między funkcjami”?

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

  1. Projektowanie funkcji z zakresem lokalnym i kodami zwrotnymi
  2. Budowanie i dołączanie wielokrotnego użytku bibliotek Bash
  3. Analizowanie flag i argumentów za pomocą getopts
  4. Przekazywanie tablic i map asocjacyjnych między funkcjami
← Powrót do Linux Command Line & Bash Scripting Mastery