Tömbök kezelése és használata a Shell scriptekben (3. oldal)

botond küldte be 2019. 03. 11., h – 17:10 időpontban

A 3. oldal tartalma

 

Folytatás

Az előző oldalon megismerhettük az indexelt tömbök elágazásokban illetve függvényekben történő felhasználását, itt folytatjuk a tömbök használatát az asszociatív tömbökkel való megismerkedéssel.

 

 

Asszociatív tömbök

Az asszociatív tömbök nagyon hasonlítanak az indexelt tömbökhöz, annyi különbséggel, hogy a szám indexeken kívül még bármilyen szöveges indexet (más néven kulcsokat) is adhatunk a tömb elemeinek azonosítójaként. Használatuk nagyrészt ugyanúgy történik, mint az indexelt tömböknél leírtaknál, de vannak eltérések is. Így ebben a fejezetben rávilágítunk ezekre a különbségekre is.

Az Asszociatív tömbök használata a Bash 4.0-ás verziójában került be, így az ennél régebbi Bash verzióban még nem volt elérhető.

Deklarálás, értékadás

Az asszociatív tömbök deklarálása annyiban tér el az indexelt tömbökétől, hogy itt kötelező a deklarálás:

declare -A asszociativ_tomb

Az indexelt tömböknél a kis -a kapcsolóval lehetett (nem kötelezően) deklarálni, itt pedig a nagy -A -val kötelező. Erre ügyeljünk!

Először próbáljunk meg értéket tölteni deklaráció nélkül egy asszociatív tömbbe, hogy lássuk mi történik ha elfelejtjük a deklarációt:

proba["elem1"]="Első elem"

Nem adott hibát. Most kérdezzük le a már ismert módon:

echo ${proba["elem1"]}

És visszaadja az "Első elem" értéket. Hurrá!

De ha az alábbi módok bármelyikén kérdezzük le:

echo ${proba["elem2"]}
echo ${proba["masik_elem"]}
echo ${proba[0]}
echo $proba

Mindegyik esetben visszakapjuk az elején megadott "Első elem" értékünket. Mi is történik itt?

A válasz egyszerű. Az asszociatív tömböt deklaráció nélkül próbáltuk meg létrehozni, ekkor a Bash egy sima indexelt tömböt hozott helyette létre. És mivel a szögletes zárójelben megadott mezőnév nullának értékelődött ki, ezért az indexelt tömb 0. elemébe került a megadott érték. Majd ezután bármelyik módon is kérdeztük le az asszociatívnak gondolt tömbünket, mindegyikre visszaadta ugyanazt az eredményt, mivel mindegyik esetben 0-nak értékelődött ki az index helyén megadott érték. A változó egyszerű ( $proba ) kiolvasása pedig az indexelt tömböknél ugyanúgy a legelső, azaz a 0. elem értékét adja vissza. (Ennek megfelelően például az echo ${proba[1]} parancsra már üreset ad vissza.)

Így tehát most van egy indexelt tömbünk, amit ha megpróbálunk újra deklarálni asszociatívként:

declare -A proba

Akkor kapunk egy ilyen hibaüzenetet:

bash: declare: proba: nem lehetséges az indexelt tömb asszociatívvá alakítása

Ezért először töröljük a tömböt, majd deklaráljuk újra asszociatívként:

unset proba
declare -A proba

Ezután már létrejön a tömbünk.

Most már adhatunk is hozzá értékeket többféleképpen is:

proba[elem1]="Első elem"
proba["elem2"]="Második elem"
proba[kecske]=káposzta

Ezután egy egysoros ciklussal ki is olvashatjuk pont úgy, mint az indexelt tömböknél:

for i in "${!proba[@]}"; do echo "$i: ${proba[$i]}" ; done
kecske: káposzta
elem3: Harmadik elem
elem2: Második elem
elem1: Első elem

Ha pedig rögtön a deklarálással egybekötve szeretnénk feltölteni értékekkel, és mindezt áttekinthető formában, akkor tehetjük így is például egy Shell scriptben:

1
2
3
4
5
6
7
8
#!/bin/bash
 
declare -A proba=(
    [elem1]="Első elem"
    [elem2]="Második elem"
    [elem3]="Harmadik elem"
    [kecske]="káposzta"
)
Ha az asszociatív tömb index->érték párosainak feltöltésekor nem használunk idézőjelet egy több szavas értéknél, akkor a következő hibaüzenetet kapjuk: "... asszociatív tömbhöz való értékadásnál meg kell adni az indexet".
Ellentétben a sima indexelt tömbökkel, ahol külön értéknek veszi ilyenkor a külön szót, itt a zárójelben felváltva várja az indexeket és az értékeket. Így ha egy több szavas érték nem kerül idézőjelbe, akkor úgy veszi, hogy kimaradt egy index, ezért hibát ad.
Ezért a hibák elkerülése végett célszerű hozzászokni az idézőjelek használatához bármilyen értéktípus megadásánál.

Többszavas kulcsok használata

Ha már szöveges indexeket/kulcsokat adhatunk meg, akkor felmerül az igény arra is, hogy többszavas kulcsokat is megadhassunk. Lássuk hogyan történik ez:

1
2
3
4
5
6
7
#!/bin/bash
declare -A tomb=(
    [több szavas kulcs 1]="Idézőjel nélkül adtuk meg a kulcsot"
    ["több szavas kulcs 2"]="Itt pedig idézőjellel"
)
 
declare -A

A végén a declare -A parancs pedig megmutatja nekünk hogyan néz ki a tömb a memóriában:

declare -A tomb=(["több szavas kulcs 1"]="Idézőjel nélkül adtuk meg a kulcsot" ["több szavas kulcs 2"]="Itt pedig idézőjellel" )

Itt láthatjuk, hogy mindkét értékadás sikeres volt, akár idézőjelekkel, akár azok nélkül adtuk meg a kulcsokat, a rendszer kipótolta őket, ahol nem kerültek megadásra. A szögletes zárójelek megléte miatt itt tudja értelmezni a több szóból álló címkéket is. Mindazonáltal célszerű ilyenkor is használni az idézőjelet, ha már több szavas neveket használunk a tömb indexeiben, hogy szabványosak maradjunk.

Értékek kiolvasása

Az asszociatív tömbökből hasonlóan olvashatjuk ki az értékeket, mint az indexelt tömböknél: Végigmehetünk az indexeken ciklusokkal, vagy direktben is hivatkozhatunk egy megadott elemre. Lényegében a ciklusban is direkt hivatkozást használunk a kiolvasásra, csak ilyenkor a ciklus adja sorban a tömb kulcsokat.

A következő példaprogramban feltöltünk egy asszociatív tömböt, majd megkeressük a elemei között a megadott mintát tartalmazó értékűeket:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
declare -A tomb=(
    [elem1]="Első elem"
    [elem2]="Második elem"
    [elem3]="Harmadik elem"
    [kecske]="káposzta"
    [több szavas kulcs 1]="Idézőjel nélkül adtuk meg a kulcsot"
    ["több szavas kulcs 2"]="Itt pedig idézőjellel"
)
 
# Keresendő karakterlánc
keres="elem"
 
for i in "${!tomb[@]}"; do
    if [[ ${tomb[$i]} == *"$keres"* ]]; then
        echo "Találat: [$i]: ${tomb[$i]}"
    fi
done

Tömbtulajdonságok kiolvasása

A tömbtulajdonságok is ugyanúgy  működnek, mint az indexelt tömböknél:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash
declare -A tomb=(
    [elem1]="Első elem"
    [elem2]="Második elem"
    [elem3]="Harmadik elem"
)
 
echo "Összes elem kiolvasása egyszerre"
echo ${tomb[*]}         # Egy mezőként bontja ki
echo ${tomb[@]}         # Külön mezőkbe bontja ki 
# (ugyanaz a működés, mint az indexelt tömböknél)
echo
echo"Aposztrófokba burkolja az elemeket."
echo "Egy mezőként bontja ki:"
printf "'%s' \n" "${tomb[*]}"
echo "Több mezőként bontja ki:"
printf "'%s' \n" "${tomb[@]}"
echo
echo "Tartomány kiolvasása"
echo ${tomb[@]:2:2}
echo
echo "Összes index kiolvasása:"
echo ${!tomb[@]}
echo
echo "Tömb elemeinek a száma:"
echo ${#tomb[@]}
echo
echo "Megadott elem karakterhosszának a kiolvasása:"
echo ${#tomb["elem1"]}

Tömbök összefűzése

Az összefűzés is ugyanúgy zajlik:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
declare -A tomb=(
    [elem1]="Első elem"
    [elem2]="Második elem"
    [elem3]="Harmadik elem"
)
 
tomb+=(
    [elem4]="negyedik elem"
    [elem5]="ötödik elem"
)
 
echo ${tomb[@]}

Annyi különbséggel, hogy itt az indexelt tömböknél megismert összefűzési módszer nem működik:

tomb=("${tomb[@]}" "2000" "következő elem")

Ilyenkor a következő hibát dobja: "tomb: '${tomb[@]}': asszociatív tömbhöz való értékadásnál meg kell adni az indexet"

Tehát itt csak a fenti módszerrel lehet hozzáfűzni, azaz, ha az indexeket is megadjuk.

 

 

Tömbök használata ciklusokban

Itt annyi az eltérés, hogy az asszociatív tömböknél lehetőség van több szavas kulcsokat is létrehozni. Ezáltal ha egy ciklusban nem jól kerül kibontásra az indexek listája, akkor itt nem ad hibát, hanem üres értékkel tér vissza, mivel itt szintaktikailag ugyan helyes a címzés, de a nem létező indexen nem talál értéket. Példa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
declare -A tomb=(
    [elem1]="Első elem"
    [elem2]="Második elem"
    [elem3]="Harmadik elem"
)
 
echo "Tömb indexek ciklusban történő helyes kibontása:"
for i in "${!tomb[@]}"; do
    echo "Index: $i, értéke: ${tomb[$i]}"
done
 
echo
echo "Helytelen használat, nincs visszatérő érték:"
for i in "${!tomb[*]}"; do
    echo "Index: $i, értéke: ${tomb[$i]}"
done

Kimenete:

Tömb indexek ciklusban történő helyes kibontása:
Index: elem3, értéke: Harmadik elem
Index: elem2, értéke: Második elem
Index: elem1, értéke: Első elem

Helytelen használat, nincs visszatérő érték:
Index: elem3 elem2 elem1, értéke:

Ahogy a példában is látható, pontosan ugyanazt az eltérő működést produkálják a @-al illetve a *-gal történő kibontások, mint az indexelt tömböknél, csak  itt az asszociatív tömböknél ez nem okoz hibát.

Tömbök használata elágazásokban

Az asszociatív tömböket pontosan ugyanúgy lehet használni az elágazásokban, mint az indexelt tömböket.

Annyi apró különbség van, hogy itt az indexeket betehetjük idézőjelek közé is. De ha már egy feltétel kifejezésén belül a tömb indexénél idézőjelet használnánk akkor az indexet aposztrófok közé tegyük, hogy elkerüljük a beágyazott idézőjeleket:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
declare -A tomb=(
    [elem1]="Első elem"
    [elem2]="Második elem"
    [elem3]="Harmadik elem"
    [több szavas]=10
)
 
# Beágyazott idézőjelek elkerüléséhez használhatunk aposztrófot a több szavas inxexeknél, de nem kötelező
[ "${tomb['több szavas']}" = 10 ] && echo "igaz" || echo "hamis"

Tömbök használata függvényekben

Az indexelt tömböktől eltérően itt nem működik megfelelően a paraméterek egy tömbként kezelése a függvénekben, mivel ezzel a módszerrel csak az értékeket tudjuk "összegyűjteni" a függvény számára. Míg az indexelt tömböknél nem volt jelentősége az indexeknek, itt viszont ilyenkor elvesznek a nevekkel ellátott kulcsaink, és csak az értékek "jutnak be" a függvénybe. Lássunk egy hasonló példát egy asszociatív tömbbel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
declare -A tomb=(
    [elem1]="Első elem"
    [elem2]="Második elem"
    [elem3]="Harmadik elem"
    [több szavas]=10
)
 
function fuggveny {
    tomb2=("$@")                    # Összes paraméter begyűjtése egy másik tömbbe
 
    for i in "${!tomb2[@]}"; do
        ertek=${tomb2[$i]}
        echo "$i: $ertek"
    done
}
 
# Függvény hívása a kibontott asszociatív tömbbel
fuggveny "${tomb[@]}"

Kimenet:

0: Harmadik elem
1: Második elem
2: Első elem
3: 10

Ahogy a kimenetben is látszik, elvesztek a deklarációkor létrehozott kulcsaink.

A példaprogram egyébként jól működik, pontosan azt csinálja, amit kell: A függvény hívásakor a "${tomb[@]}" szintaxissal kibontottuk az asszociatív tömb értékeit, és ezt kapta meg a függvény. Ami utána a 10. sorban létrehozott egy új indexelt tömböt a kapott értékekkel. Tehát az asszociatív tömbünkből létrejött egy indexelt tömb másolat, aminek az értékei ugyan megmaradtak, de a kulcsok elvesztek belőle, illetve sorszámokká alakultak.

A jó hír viszont, hogy a név szerinti változó hivatkozással működik szépen a dolog.

Név szerinti paraméterátadás

A korábban az indexelt tömbnél már megismert hasonló példa itt is működik:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
declare -A tomb=(
    [elem1]="Első elem"
    [elem2]="Második elem"
    [elem3]="Harmadik elem"
    [több szavas]=10
)
 
function fuggveny {
    declare -n param1=$1
    # A declare parancs '-n' kapcsolója oldja meg a név szerinti hivatkozást
 
    for i in "${!param1[@]}"; do
        ertek=${param1[$i]}
        echo "$i: $ertek"
    done
}
 
# Függvény hívása csak a tömb változónak a nevével
fuggveny tomb

A kimenete pedig:

elem3: Harmadik elem
elem2: Második elem
elem1: Első elem
több szavas: 10

Ezzel a módszerrel tehát megmaradtak a kulcsok is és az értékek is.

Itt viszont ügyelni kell, hogy a függvényben ha módosítjuk az így létrehozott változókat, akkor az az eredeti változóra is kihat a program globális névterében. Erre egy példa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/bash
 
# Tömb deklarálása a globális névtérben
declare -A tomb=(
    [elem1]="Első elem"
    [elem2]="Második elem"
    [elem3]="Harmadik elem"
)
 
function fuggveny {
    declare -n param=$1
 
    echo "Eredeti tömb tartalma a módosítás előtt:"
    for i in "${!param[@]}"; do
        ertek=${param[$i]}
        echo "$i: $ertek"
    done
 
    # A függvényen belül még hozzáfűzünk újabb elemeket a tömbhöz.
    param+=(
        [ujabb elem]="Függvényben adtuk hozzá"
        [ujabb elem2]="Ezt is a függvényben adtuk hozzá"
    )
}
 
# Függvény hívása a név szerinti paraméterrel
fuggveny tomb
 
echo
echo "Az eredeti tömb tartalma a globális névtérben a függvény lefutása után:"
 
for i in "${!tomb[@]}"; do
    ertek=${tomb[$i]}
    echo "$i: $ertek"
done

A kimenet pedig:

Eredeti tömb tartalma a módosítás előtt:
elem3: Harmadik elem
elem2: Második elem
elem1: Első elem

Az eredeti tömb tartalma a globális névtérben a függvény lefutása után:
elem3: Harmadik elem
elem2: Második elem
elem1: Első elem
ujabb elem: Függvényben adtuk hozzá
ujabb elem2: Ezt is a függvényben adtuk hozzá

Látható, hogy a függvényben történt módosítás után az eredeti változó tartalma is megváltozott. Mindez azért, mert egy név szerinti hivatkozást hoztunk létre a függvényben az eredeti változóra, nem pedig új változót hoztunk létre.

 

A következő oldalon folytatjuk a tömbök globális névtérbe történő exportálásával...

 

 

Lapozó

Ez a leírás több oldalból áll: