Bemästra Linux-kommandoraden och Bash-skriptning · Lektion

Skicka arrayer och associativa mappar mellan funktioner

Använd namerefs och indirekt expansion för att säkert skicka komplexa datastrukturer till och från funktioner.

Lektion 4 av 413 steg

Skicka arrayer och associativa mappar mellan funktioner är en gratis lektion i Bemästra Linux-kommandoraden och Bash-skriptning på CoddyKit. Detta är lektion 4 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Bemästra Linux-kommandoraden och Bash-skriptning, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Bemästra Linux-kommandoraden och Bash-skriptning innehåller totalt 4 lektioner.

Varför det är knepigt att skicka arrayer till funktioner

I Bash är arrayer och associativa arrayer inte förstaklassvärden. När du försöker skicka en array till en funktion kan du inte bara använda $my_array — det expanderar endast det första elementet.

Se det här felaktiga försöket:

my_func "${my_array[@]}"

Funktionen tar emot en platt lista med ord. Den har inget sätt att veta var ett element slutar och nästa börjar, och arrayens struktur går helt förlorad.

Bash erbjuder två tillförlitliga mekanismer för att skicka komplexa datastrukturer:

  • Namerefs (declare -n) — en referens till en annan variabel via dess namn (Bash 4.3+)
  • Indirekt expansion (${!varname[@]}) — åtkomst till en variabel vars namn lagras i en annan variabel (äldre Bash)

I den här lektionen går vi igenom båda teknikerna, så att du kan skriva robusta och återanvändbara funktioner som fungerar med arrayer och mappar.

Skicka en array via namn med en nameref

Det renaste moderna tillvägagångssättet är en nameref: du skickar arrayens namn som en sträng och deklarerar inuti funktionen en lokal variabel som refererar till den.

Viktig syntax:

  • declare -n ref="$1" — ref är nu ett alias för det variabelnamn som skickades som det första argumentet
  • Du läser, itererar över och ändrar ref precis som om den vore den ursprungliga arrayen

Detta undviker att hela arrayen kopieras och låter funktionen arbeta med arrayer av valfri storlek.

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

Ändra anroparens array inifrån en funktion

Eftersom en nameref är ett äkta alias innebär skrivningar via namerefen att anroparens ursprungliga variabel ändras. Du behöver inte skriva ut värden och skicka tillbaka dem eller använda globala variabler.

Det gör namerefs idealiska för funktioner som transformerar arrayer på plats — till exempel för att normalisera strängar, filtrera element eller sortera.

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

Returnera en ny array via en nameref

En funktion kan också skriva till en array som anroparen tillhandahåller genom att ta emot dess namn som en utdataparameter. Anroparen skapar en tom array, skickar dess namn och funktionen fyller den med innehåll.

Detta mönster motsvarar den klassiska C-konventionen att skicka en pekare till en resultatbuffer — med skillnaden att det här görs helt i skalet.

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

Fallgropen med nameref-kollisioner

Det finns en kritisk fallgrop med namerefs: om den lokala nameref-variabeln har samma namn som anroparens variabel hamnar Bash i en oändlig självreferensloop och skriptet misslyckas.

Om anroparen till exempel kallar sin array arr och funktionen kör declare -n arr="$1", pekar namerefen arr på sig själv.

Konvention för att undvika detta: prefixa interna nameref-variabler med ett understreck, till exempel _arr, _ref eller _out. Anropare använder nästan aldrig sådana namn, vilket gör kollisioner extremt ovanliga.

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

Skicka associativa arrayer (mappar) via namn

Associativa arrayer (declare -A) fungerar på samma sätt med namerefs. Skicka mappens namn som en sträng och deklarera en nameref inuti funktionen — då får du full läs- och skrivåtkomst till alla nyckel-värde-par.

Detta är ovärderligt för konfigurationsmappar, alternativtolkare och uppslagstabeller som måste delas mellan 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

Slå samman två mappar med en nameref

Eftersom namerefs ger funktionen ett aktivt alias kan du implementera verktyg som sammanslagning av mappar på ett elegant sätt. Skicka målmappen och en eller flera källmappar via namn; funktionen itererar över källnycklarna och skriver dem till målmappen.

Namerefs gör det möjligt att skriva en enda generell funktion, merge_map, som fungerar med alla associativa arrayer i skriptet.

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

Indirekt expansion: kompatibilitet med äldre Bash

Om du måste stödja Bash 4.0–4.2 (eller äldre standardskal i macOS) är declare -n inte tillgängligt. Alternativet är indirekt expansion med ${!varname}.

Indirekt expansion tolkar värdet i varname som ett variabelnamn och returnerar den variabelns värde. För arrayer kombinerar du den med en särskild syntax för att hämta alla element.

Begränsningar jämfört med namerefs:

  • I praktiken skrivskyddad (för att skriva tillbaka krävs eval, vilket medför injektionsrisker)
  • Nycklar i associativa arrayer kan inte itereras säkert utan eval
  • Använd namerefs när det är möjligt och använd indirekt expansion som reservlösning endast när det behövs
#!/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"

Bygga ett modulärt skriptbibliotek

I professionella Bash-projekt lagras verktygsfunktioner i biblioteksfiler (till exempel lib/array_utils.sh) och läses in av de skript som behöver dem.

Mönstret är:

  • Varje biblioteksfil definierar endast funktioner — inga sidoeffekter på toppnivå
  • Förhindra att filen läses in flera gånger med en vaktvariabel: [[ -n $__LIB_LOADED ]] && return; __LIB_LOADED=1
  • Anropande skript använder source eller . för att läsa in biblioteket
  • Funktioner utbyter komplexa data via namerefs, så att all logik hålls självständig

Detta ger skript som går att testa, återanvända och underhålla — inte engångslösningar med skalskript.

Praktiskt exempel: alternativtolkare som returnerar en mapp

Ett verkligt användningsfall är en kommandoradsbaserad alternativtolkare som fyller i en associativ array som anroparen tillhandahåller. Anroparen skickar arrayens namn som det första argumentet, följt av de faktiska "$@"-argumenten.

Detta mönster gör att alla skript kan återanvända samma tolkare utan att kopiera kod eller förlita sig på globala 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]}"

Kombinera in- och utdata-namerefs i samma funktion

Funktioner kan ha flera namerefs samtidigt — en för indata och en för utdata. Det möjliggör rena transformationsfunktioner: de läser från en källstruktur, beräknar ett resultat och skriver det till en målstruktur, allt utan att påverka det globala tillståndet.

Genom att använda separata namn med understreck som prefix för varje nameref elimineras kollisioner, även när anroparens variabler har enkla namn.

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

Kunskapskontroll: namerefs och arrayer som argument

Testa dina kunskaper om att skicka arrayer och mappar mellan funktioner i Bash.

Sammanfattning: skicka komplexa datastrukturer i Bash

Det här lärde du dig i lektionen:

  • Arrayer kan inte skickas som värden — skicka i stället variabelns namn som en sträng.
  • declare -n ref="$varname" skapar en nameref: ett aktivt alias till anroparens variabel. Både läsningar och skrivningar går vidare till originalet. Kräver Bash 4.3+.
  • Nameref-kollision uppstår när den interna och externa variabeln har samma namn. Förhindra detta genom att prefixa interna namerefs (till exempel _arr eller _map).
  • Utdataparametrar fungerar genom att namnet på en tom resultatarray eller resultatmapp skickas; funktionen fyller den via en nameref.
  • Flera namerefs i samma funktion möjliggör rena transformationer från indata till utdata utan globala sidoeffekter.
  • Indirekt expansion (${!varname[@]} + eval) är en äldre reservlösning för Bash < 4.3, men medför injektionsrisker och är i praktiken skrivskyddad.
  • Kombinera dessa tekniker med biblioteksfiler som läses in för att bygga modulära, återanvändbara och testbara Bash-kodbaser.
Gratis att börja

Lär dig Bash med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
22
Lektioner
88

Vanliga frågor

Är lektionen ”Skicka arrayer och associativa mappar mellan funktioner” gratis?

Ja – hela texten till ”Skicka arrayer och associativa mappar mellan funktioner” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Bemästra Linux-kommandoraden och Bash-skriptning, kan Ni uppgradera till CoddyKit PRO. Kursen i Bemästra Linux-kommandoraden och Bash-skriptning innehåller totalt 4 lektioner.

Vad lär jag mig i ”Skicka arrayer och associativa mappar mellan funktioner”?

Använd namerefs och indirekt expansion för att säkert skicka komplexa datastrukturer till och från funktioner. Ni övar på Bemästra Linux-kommandoraden och Bash-skriptning med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Bemästra Linux-kommandoraden och Bash-skriptning?

Du behöver inga förkunskaper. Utbildningen i Bemästra Linux-kommandoraden och Bash-skriptning på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.

Hur lång tid tar lektionen ”Skicka arrayer och associativa mappar mellan funktioner”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Bemästra Linux-kommandoraden och Bash-skriptning-lektionen?

Ja. Varje Bemästra Linux-kommandoraden och Bash-skriptning-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Utforma funktioner med lokal omfattning och returkoder
  2. Bygga och läsa in återanvändbara Bash-bibliotek
  3. Parsa flaggor och argument med getopts
  4. Skicka arrayer och associativa mappar mellan funktioner
← Tillbaka till Bemästra Linux-kommandoraden och Bash-skriptning