Intensiv DevOps-uddannelse · Lektion

Overførsel af arrays og associative maps mellem funktioner

Brug namerefs og indirekte expansion til sikkert at overføre komplekse datastrukturer til og fra funktioner.

Lektion 4 af 413 trin

Overførsel af arrays og associative maps mellem funktioner er en gratis Intensiv DevOps-uddannelse-lektion på CoddyKit. Dette er lektion 4 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Intensiv DevOps-uddannelse, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Intensiv DevOps-uddannelse-kurset indeholder 4 lektioner i alt.

Hvorfor det er tricky at overføre arrays til funktioner

I Bash er arrays og associative arrays ikke værdier af første rang. Når du forsøger at overføre et array til en funktion, kan du ikke bare bruge $my_array — det udvider kun det første element.

Overvej dette fejlslagne forsøg:

my_func "${my_array[@]}"

Funktionen modtager en flad liste af ord. Den har ingen mulighed for at vide, hvor ét element slutter, og det næste begynder, og array-strukturen går helt tabt.

Bash tilbyder to pålidelige mekanismer til at overføre komplekse datastrukturer:

  • Namerefs (declare -n) — en reference til en anden variabel via dens navn (Bash 4.3+)
  • Indirekte udvidelse (${!varname[@]}) — adgang til en variabel, hvis navn er gemt i en anden variabel (ældre Bash)

Denne lektion gennemgår begge teknikker, så du kan skrive robuste, genanvendelige funktioner, der arbejder med arrays og maps.

Overførsel af et array via navn med en nameref

Den reneste moderne tilgang er en nameref: du overfører arrayets navn som en streng, og inde i funktionen erklærer du en lokal variabel, der refererer til det.

Den vigtigste syntaks:

  • declare -n ref="$1" — ref er nu et alias for den variabel, hvis navn blev overført som det første argument
  • Du læser, gennemløber og ændrer ref præcis som det oprindelige array

Det undgår at kopiere hele arrayet og lader funktionen arbejde med arrays af enhver størrelse.

#!/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

Ændring af den kaldende funktions array inde i en funktion

Fordi en nameref er et ægte alias, ændrer skrivninger gennem namerefen den kaldende funktions oprindelige variabel. Der er ikke behov for at skrive værdier tilbage med echo eller bruge globale variabler.

Det gør namerefs ideelle til funktioner, der transformerer arrays på stedet — for eksempel ved at normalisere strenge, filtrere elementer eller sortere.

#!/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[@]}"

Returnering af et nyt array gennem en nameref

En funktion kan også skrive til et array, som den kaldende funktion har leveret, ved at acceptere dets navn som en outputparameter. Den kaldende funktion opretter et tomt array, overfører dets navn, og funktionen udfylder det.

Dette mønster minder om den klassiske C-idiom med at overføre en pointer til en resultatbuffer — bortset fra at det her udføres helt i shell.

#!/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[@]}"

Fælden med nameref-kollisioner

Der er én kritisk faldgrube ved namerefs: Hvis den lokale nameref-variabel har samme navn som den kaldende funktions variabel, går Bash ind i en uendelig selvreferenceløkke, og scriptet mislykkes.

Hvis den kaldende funktion for eksempel kalder sit array arr, og funktionen udfører declare -n arr="$1", peger namerefen arr på sig selv.

Konvention, der undgår dette: Sæt et underscore foran interne nameref-variabler (f.eks. _arr, _ref, _out). Kaldende funktioner bruger næsten aldrig sådanne navne, så kollisioner bliver ekstremt sjældne.

#!/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

Overførsel af associative arrays (maps) via navn

Associative arrays (declare -A) fungerer på samme måde med namerefs. Overfør mappets navn som en streng, og erklær en nameref inde i funktionen — så får du fuld læse- og skriveadgang til alle nøgleværdi-par.

Det er uvurderligt til konfigurationsmaps, optionfortolkere og opslagstabeller, der skal deles mellem biblioteksfunktioner.

#!/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

Sammenfletning af to maps med en nameref

Fordi namerefs giver funktionen et aktivt alias, kan du implementere hjælpefunktioner som sammenfletning af maps på en enkel måde. Overfør destinationsmappet og et eller flere kildemaps via navn; funktionen gennemløber kildekeys og skriver dem ind i destinationen.

Namerefs gør det muligt at skrive én generisk merge_map-funktion, der fungerer med alle associative arrays i dit script.

#!/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

Indirekte udvidelse: kompatibilitet med ældre Bash

Hvis du skal understøtte Bash 4.0–4.2 (eller ældre standardskaller på macOS), er declare -n ikke tilgængelig. Alternativet er indirekte udvidelse med ${!varname}.

Indirekte udvidelse fortolker værdien af varname som et variabelnavn og returnerer variablens værdi. For arrays kombinerer du den med en særlig syntaks for at få alle elementer.

Begrænsninger sammenlignet med namerefs:

  • I praksis skrivebeskyttet (tilbageskrivning kræver eval, som medfører risiko for injektion)
  • Associative array-nøgler kan ikke gennemløbes sikkert uden eval
  • Brug namerefs, hvor det er muligt; brug kun indirekte udvidelse som alternativ, når det er nødvendigt
#!/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"

Opbygning af et modulært scriptbibliotek

I professionelle Bash-projekter gemmes hjælpefunktioner i biblioteksfiler (f.eks. lib/array_utils.sh) og indlæses af de scripts, der har brug for dem.

Mønstret er:

  • Hver biblioteksfil definerer kun funktioner — ingen bivirkninger på øverste niveau
  • Undgå at indlæse filen flere gange med en markørvariabel: [[ -n $__LIB_LOADED ]] && return; __LIB_LOADED=1
  • Forbrugerscripts bruger source eller . til at indlæse biblioteket
  • Funktioner udveksler komplekse data via namerefs, så al logik forbliver selvstændig

Det giver scripts, der kan testes, genbruges og vedligeholdes — ikke engangshacks i shell.

Praktisk eksempel: optionfortolker, der returnerer et map

Et eksempel fra virkeligheden: en fortolker af kommandolinjeoptioner, der udfylder et associativt array, som den kaldende funktion har leveret. Den kaldende funktion overfører arrayets navn som det første argument efterfulgt af de faktiske "$@"-argumenter.

Dette mønster lader ethvert script genbruge den samme fortolker uden at kopiere kode eller være afhængigt af globale variabler.

#!/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]}"

Kombination af input- og outputnamerefs i én funktion

Funktioner kan have flere namerefs samtidig — én til input og én til output. Det muliggør rene transformationsfunktioner: De læser fra en kildestruktur, beregner et resultat og skriver det til en destinationsstruktur, alt sammen uden at ændre global tilstand.

Ved at bruge forskellige navne med underscore foran til hver nameref undgår du kollisioner, selv når de kaldende funktioners variabler har enkle navne.

#!/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

Test din viden: namerefs og overførsel af arrays

Test din forståelse af, hvordan arrays og maps overføres mellem funktioner i Bash.

Opsummering: Overførsel af komplekse datastrukturer i Bash

Dette har du lært i lektionen:

  • Arrays kan ikke overføres som værdier — overfør i stedet variablens navn som en streng.
  • declare -n ref="$varname" opretter en nameref: et aktivt alias til den kaldende funktions variabel. Både læsninger og skrivninger går videre til den oprindelige variabel. Kræver Bash 4.3+.
  • Nameref-kollision opstår, når den interne og den eksterne variabel har samme navn. Forebyg det ved at sætte et underscore foran interne namerefs (f.eks. _arr, _map).
  • Outputparametre fungerer ved at overføre navnet på et tomt resultat-array eller -map; funktionen udfylder det gennem en nameref.
  • Flere namerefs i én funktion muliggør rene transformationer fra input til output uden globale bivirkninger.
  • Indirekte udvidelse (${!varname[@]} + eval) er et ældre alternativ til Bash < 4.3, men medfører risiko for injektion og er i praksis skrivebeskyttet.
  • Kombiner disse teknikker med indlæste biblioteksfiler for at opbygge modulære, genanvendelige og testbare Bash-kodebaser.
Gratis at komme i gang

Lær Intensiv DevOps-uddannelse med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
142
Lektioner
568

Ofte stillede spørgsmål

Er lektionen “Overførsel af arrays og associative maps mellem funktioner” gratis?

Ja — alle 3 lektioner i læringssporet Intensiv DevOps-uddannelse, inklusive “Overførsel af arrays og associative maps mellem funktioner”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Intensiv DevOps-uddannelse-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Overførsel af arrays og associative maps mellem funktioner”?

Brug namerefs og indirekte expansion til sikkert at overføre komplekse datastrukturer til og fra funktioner. Du øver dig i Intensiv DevOps-uddannelse med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Intensiv DevOps-uddannelse?

Der kræves ingen tidligere erfaring. Intensiv DevOps-uddannelse på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 4 af 4.

Hvor lang tid tager lektionen “Overførsel af arrays og associative maps mellem funktioner”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Intensiv DevOps-uddannelse-lektion?

Ja. Alle Intensiv DevOps-uddannelse-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Design af funktioner med lokalt scope og return codes
  2. Opbygning og sourcing af genanvendelige Bash-biblioteker
  3. Parsing af flags og argumenter med getopts
  4. Overførsel af arrays og associative maps mellem funktioner
← Tilbage til Intensiv DevOps-uddannelse