DevOps-bootcamp · Oppitunti

Taulukoiden ja assosiatiivisten karttojen välittäminen funktioiden välillä

Käyttäkää nameref-viittauksia ja epäsuoraa laajennusta monimutkaisten tietorakenteiden turvalliseen välittämiseen funktioihin ja niistä pois.

Oppitunti 4/413 vaihetta

Taulukoiden ja assosiatiivisten karttojen välittäminen funktioiden välillä on ilmainen DevOps-bootcamp-oppitunti CoddyKitissä. Tämä on oppitunti 4/4. Voit lukea tästä oppimispolusta kokonaan mitkä tahansa 3 oppituntia ilmaiseksi — sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä käytännön harjoittelun sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Oppitunti kuuluu DevOps-bootcamp-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. DevOps-bootcamp-kurssilla on yhteensä 4 oppituntia.

Miksi taulukoiden välittäminen funktioille on hankalaa

Bashissa taulukot ja assosiatiiviset taulukot eivät ole ensiluokkaisia arvoja. Kun yrität välittää taulukon funktiolle, et voi käyttää suoraan muotoa $my_array — se laajentaa vain ensimmäisen alkion.

Tarkastellaan tätä toimimatonta yritystä:

my_func "${my_array[@]}"

Funktio saa litteän listan sanoja. Se ei voi tietää, missä yksi alkio päättyy ja seuraava alkaa, ja taulukon rakenne katoaa kokonaan.

Bash tarjoaa kaksi luotettavaa tapaa välittää monimutkaisia tietorakenteita:

  • Nimiviitteet (declare -n) — viite toiseen muuttujaan sen nimen perusteella (Bash 4.3+)
  • Epäsuora laajennus (${!varname[@]}) — käsiksi pääseminen muuttujaan, jonka nimi on tallennettu toiseen muuttujaan (vanhemmat Bash-versiot)

Tässä oppitunnissa käsitellään molemmat tekniikat, jotta voitte kirjoittaa vankkoja ja uudelleenkäytettäviä funktioita, jotka toimivat taulukoiden ja karttojen kanssa.

Taulukon välittäminen nimen perusteella nimiviitteen avulla

Siistein nykyaikainen tapa on käyttää nimiviitettä: välität taulukon nimen merkkijonona ja määrittelet funktion sisällä paikallisen muuttujan, joka viittaa siihen.

Keskeinen syntaksi:

  • declare -n ref="$1" — ref on nyt alias muuttujalle, jonka nimi välitettiin ensimmäisenä argumenttina
  • Luet, iteroit ja muokkaat ref-muuttujaa aivan kuin se olisi alkuperäinen taulukko

Näin vältetään koko taulukon kopioiminen, ja funktio voi käsitellä minkä tahansa kokoisia taulukoita.

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

Kutsujan taulukon muokkaaminen funktion sisällä

Koska nimiviite on todellinen alias, nimiviitteen kautta tehdyt kirjoitukset muuttavat kutsujan alkuperäistä muuttujaa. Arvoja ei tarvitse tulostaa takaisin eikä yleisiä muuttujia tarvitse käyttää.

Tämän vuoksi nimiviitteet sopivat erinomaisesti funktioille, jotka muuntavat taulukoita paikallaan — esimerkiksi merkkijonojen normalisointiin, alkioiden suodattamiseen tai lajitteluun.

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

Uuden taulukon palauttaminen nimiviitteen kautta

Funktio voi myös kirjoittaa kutsujan antamaan taulukkoon hyväksymällä sen nimen tulosparametrina. Kutsuja luo tyhjän taulukon, välittää sen nimen, ja funktio täyttää sen.

Tämä toimintamalli vastaa klassista C-kielen tapaa välittää osoitin tulospuskuriin — paitsi että tässä kaikki tehdään shellissä.

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

Nimiviitteen nimiristiriidan sudenkuoppa

Nimiviitteisiin liittyy yksi kriittinen sudenkuoppa: jos paikallisella nimiviitemuuttujalla on sama nimi kuin kutsujan muuttujalla, Bash joutuu loputtomaan itseensä viittaavaan silmukkaan ja komentosarja epäonnistuu.

Jos kutsuja esimerkiksi nimeää taulukkonsa arr ja funktio suorittaa declare -n arr="$1", nimiviite arr viittaa itseensä.

Vältä tämä käyttämällä seuraavaa käytäntöä: lisää sisäisten nimiviitemuuttujien eteen alaviiva (esimerkiksi _arr, _ref, _out). Kutsujat käyttävät tällaisia nimiä erittäin harvoin, joten ristiriidat ovat hyvin epätodennäköisiä.

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

Assosiatiivisten taulukoiden (karttojen) välittäminen nimen perusteella

Assosiatiiviset taulukot (declare -A) toimivat nimiviitteiden kanssa samalla tavalla. Välitä kartan nimi merkkijonona ja määrittele funktion sisällä nimiviite — saat täydet luku- ja kirjoitusoikeudet kaikkiin avain-arvo-pareihin.

Tämä on erittäin hyödyllistä määritys kartoissa, valitsinten jäsentimissä ja hakutaulukoissa, joita useiden kirjastofunktioiden täytyy käyttää yhdessä.

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

Kahden kartan yhdistäminen nimiviitteen avulla

Koska nimiviitteet antavat funktiolle suoran aliaksen, voit toteuttaa esimerkiksi karttojen yhdistämisen siististi. Välitä kohdekartan ja yhden tai useamman lähdekartan nimet; funktio käy lähteiden avaimet läpi ja kirjoittaa ne kohteeseen.

Nimiviitteiden ansiosta voit kirjoittaa yhden yleiskäyttöisen merge_map-funktion, joka toimii kaikkien komentosarjasi assosiatiivisten taulukoiden kanssa.

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

Epäsuora laajennus: yhteensopivuus vanhempien Bash-versioiden kanssa

Jos sinun on tuettava Bash-versioita 4.0–4.2 (tai vanhempia macOS:n oletuskuoria), declare -n ei ole käytettävissä. Vaihtoehtona on epäsuora laajennus käyttäen muotoa ${!varname}.

Epäsuora laajennus tulkitsee varname-muuttujan arvon muuttujan nimeksi ja palauttaa kyseisen muuttujan arvon. Taulukoiden kanssa siihen yhdistetään erityinen syntaksi kaikkien alkioiden hakemista varten.

Rajoitukset nimiviitteisiin verrattuna:

  • Käytännössä vain lukuun (takaisin kirjoittaminen edellyttää eval-komentoa, johon liittyy injektioriskejä)
  • Assosiatiivisen taulukon avaimia ei voi iteroida turvallisesti ilman eval-komentoa
  • Käyttäkää nimiviitteitä aina kun mahdollista ja turvautukaa epäsuoraan laajennukseen vain tarvittaessa
#!/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"

Modulaarisen komentosarjakirjaston rakentaminen

Ammattimaisissa Bash-projekteissa apufunktiot tallennetaan kirjastotiedostoihin (esimerkiksi lib/array_utils.sh), ja niitä käyttävät komentosarjat lataavat tiedostot lähdekoodiksi.

Toimintamalli on seuraava:

  • Jokainen kirjastotiedosto määrittelee vain funktioita — ylätason sivuvaikutuksia ei ole
  • Estä tiedoston lataaminen lähdekoodiksi useita kertoja vartiomuuttujalla: [[ -n $__LIB_LOADED ]] && return; __LIB_LOADED=1
  • Käyttäjäkomentosarjat lataavat kirjaston komennolla source tai .
  • Funktiot vaihtavat monimutkaisia tietoja nimiviitteiden avulla, jolloin kaikki logiikka pysyy itsenäisenä

Tuloksena on testattavia, uudelleenkäytettäviä ja ylläpidettäviä komentosarjoja — ei vain kertakäyttöisiä shell-virityksiä.

Käytännön esimerkki: kartan palauttava valitsin jäsentävä funktio

Käytännön käyttötapaus: komentorivin valitsin jäsentävä funktio, joka täyttää kutsujan antaman assosiatiivisen taulukon. Kutsuja välittää taulukon nimen ensimmäisenä argumenttina ja sen jälkeen varsinaiset "$@"-argumentit.

Tämän toimintamallin ansiosta mikä tahansa komentosarja voi käyttää samaa jäsentäjää uudelleen ilman koodin kopioimista tai yleisiin muuttujiin turvautumista.

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

Syöte- ja tulosnimiviitteiden yhdistäminen samassa funktiossa

Funktiossa voi olla useita nimiviitteitä samanaikaisesti — yksi syötteelle ja yksi tulosteelle. Näin voidaan toteuttaa puhtaita muunnosfunktioita: ne lukevat lähderakenteesta, laskevat tuloksen ja kirjoittavat sen kohderakenteeseen koskematta lainkaan yleiseen tilaan.

Kullekin nimiviitteelle käytettävät erilliset alaviivalla alkavat nimet estävät ristiriidat, vaikka kutsujan muuttujilla olisi yksinkertaiset nimet.

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

Tietojen tarkistus: nimiviitteet ja taulukoiden välittäminen

Testaa ymmärryksesi taulukoiden ja karttojen välittämisestä Bash-funktioiden välillä.

Kertaus: monimutkaisten tietorakenteiden välittäminen Bashissa

Tässä ovat oppitunnin tärkeimmät opit:

  • Taulukoita ei voi välittää arvona — välitä sen sijaan muuttujan nimi merkkijonona.
  • declare -n ref="$varname" luo nimiviitteen: suoran aliaksen kutsujan muuttujaan. Sekä luku että kirjoitus kohdistuvat alkuperäiseen muuttujaan. Vaatii Bash-version 4.3 tai uudemman.
  • Nimiviitteen nimiristiriita syntyy, kun sisäisellä ja ulkoisella muuttujalla on sama nimi. Estä tämä lisäämällä sisäisten nimiviitteiden eteen esimerkiksi _arr tai _map.
  • Tulosparametrit toimivat välittämällä tyhjän tulostaulukon tai -kartan nimen; funktio täyttää sen nimiviitteen kautta.
  • Useat nimiviitteet samassa funktiossa mahdollistavat puhtaat syötteestä tulosteeksi -muunnokset ilman yleisiä sivuvaikutuksia.
  • Epäsuora laajennus (${!varname[@]} + eval) on vanhempi vaihtoehto Bash-versioille < 4.3, mutta siihen liittyy injektioriskejä, ja käytännössä se soveltuu vain lukemiseen.
  • Yhdistämällä nämä tekniikat lähdekoodiksi ladattaviin kirjastotiedostoihin voit rakentaa modulaarisia, uudelleenkäytettäviä ja testattavia Bash-koodikantoja.
Aloita maksutta

Opi DevOps-bootcamp tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
142
Oppitunnit
568

Usein kysytyt kysymykset

Onko oppitunti ”Taulukoiden ja assosiatiivisten karttojen välittäminen funktioiden välillä” ilmainen?

Kyllä — voit lukea täällä verkossa kokonaan ilmaiseksi mitkä tahansa DevOps-bootcamp-oppimispolun 3 oppituntia, myös oppitunnin “Taulukoiden ja assosiatiivisten karttojen välittäminen funktioiden välillä”. Sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä interaktiiviset harjoitukset sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. DevOps-bootcamp-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Taulukoiden ja assosiatiivisten karttojen välittäminen funktioiden välillä”?

Käyttäkää nameref-viittauksia ja epäsuoraa laajennusta monimutkaisten tietorakenteiden turvalliseen välittämiseen funktioihin ja niistä pois. Harjoittelet DevOps-bootcamp-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni DevOps-bootcamp-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin DevOps-bootcamp-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 4/4.

Kuinka kauan ”Taulukoiden ja assosiatiivisten karttojen välittäminen funktioiden välillä”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä DevOps-bootcamp-oppitunnilla?

Kyllä. Jokainen DevOps-bootcamp-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Funktioiden suunnittelu paikallisella näkyvyysalueella ja paluuarvokoodeilla
  2. Uudelleenkäytettävien Bash-kirjastojen rakentaminen ja lataaminen
  3. Lippujen ja argumenttien jäsentäminen getoptsilla
  4. Taulukoiden ja assosiatiivisten karttojen välittäminen funktioiden välillä
← Takaisin: DevOps-bootcamp