Go to the first, previous, next, last section, table of contents.


Tömbök az awk-ban

Egy tömb az értékek, azaz elemek, táblája. A tömbök elemeit az indexek különböztetik meg. Az indexek lehetnek számok vagy szövegek. Mivel az awk egyetlen halmazt tart fenn a változók, tömbök és függvények neveire (see section Felhasználó által definiált függvények), ezért nem használható ugyanaz a név változóként és tömbként ugyanabban az awk programban.

Bevezetés a tömbökbe

Az awk nyelv egydimenziós tömböket biztosít összetartozó szövegek és számok csoportban tárolására.

Minden awk tömbnek kell legyen neve. A tömbök nevének ugyanaz a formája, mint a változók neveinek; bármilyen érvényes változónév egy érvényes tömbnév lehet. De nem használhatod ugyanazt a nevet változónak és tömbnek is ugyanabban az awk programban.

Az awk tömbök felületesen hasonlítanak más programozási nyelvek tömbjeire; de alapvetô különbségek vannak. Az awk-ban nem kell megadni a tömb méretét mielôtt használnánk. Ráadásul bármilyen szöveg vagy szám használható indexként, nem csak egymás utáni egész számok.

Más programozási nyelvekben egy tömböt definiálni kell, és meg kell adni, hogy hány elemet fog tartalmazni. Ezekben a nyelvekben, a definiálás egy folyamatos memóriatömböt foglal le az elemek számára. A tömb indexei pozitív, egész számok kell legyenek; például, az elsô elemre a zérus indexel lehet hivatkozni, ami a memóriablokk legelején helyezkedik el. A második elemre az egyes indexel lehet hivatkozni, ami közvetlenül az elsô mellett helyezkedik el, és így tovább. Lehetetlen több elemet adni a tömbhöz, mivel csak az adott méretű hely lett lefoglalva. (Néhány programozási nyelv megenged tetszôleges kezdô vagy vég indexet, pl. `15 .. 27', de a tömb mérete csak adott méretű lehet.)

Koncepcionálisan egy négy elemű tömb így néz ki, ha az elemek a 8, a "foo", a "" és a 30:

Csak az elemek tárolódnak; az indexek implicit módon következnek az értékekbôl. A 8 a zérus indexű elem értéke, mivel a 8 a zérus pozíciójú elemben található.

Az awk tömbjei asszociatívak. Ez azt jelenti, hogy a tömb index és érték párok gyűjteménye:

Elem 4     Érték 30
Elem 2     Érték "foo"
Elem 1     Érték 8
Elem 3     Érték ""

A párokat kevert sorrendben közöltük mivel a sorrend nem számít.

Az asszociatív tömbök egy nagy elônye, hogy új elemeket bármikor hozzá lehet adni. Például tegyük fel, hogy a fenti tömbhöz egy tizedik elemet kell hozzáadni aminek az értéke "number ten". Az eredmény:

Elem 10    Érték "number ten"
Elem 4     Érték 30
Elem 2     Érték "foo"
Elem 1     Érték 8
Elem 3     Érték ""

most a tömb szórt, "sparse", ami azt jelenti, hogy bizonyos indexű elemek hiányoznak: a tömbnek megvannak az 1--4 -ig terjedô elemei és a 10. eleme, de nincs 5, 6, 7, 8 vagy 9 -es eleme.

Az asszociatív tömbök egy másik következménye, hogy az indexeknek nem kell pozitív egész számoknak lenniük. Bármilyen szám vagy szöveg indexként használható. Például itt közlünk egy tömböt, ami lefordítja az angol szavakat franciára:

Elem "dog" Érték "chien"
Elem "cat" Érték "chat"
Elem "one" Érték "un"
Elem 1     Érték "un"

Itt az egynek nem csak a szöveg formája hanem a szám formája is szerepel a szótárban -- így illusztrálható, hogy egy tömbben szám és szöveg index is lehet. (Valójában a tömb indexe mindig szöveg; ezt részletesen tárgyaljuk a section Számok használata mint tömb index alatt.)

Az IGNORECASE értéke nincs hatással a tömb indexére. Pontosan ugyanazt a szöveget kell használni a tömb eléréséhez, mint amit az érték tárolásánál használtunk.

Amikor az awk hoz létre egy tömböt, pl. a split beépített függvénnyel, akkor az indexek egymás utáni egész számok lesznek egytôl kezdve. (See section Szövegmanipuláló beépített függvények.)

Egy tömb elemeinek elérése

Egy tömb alapvetô használati módja az elemeinek az elérése. Az elemek elérhetôek az alábbi formában:

array[index]

Itt az array a tömb neve. Az index kifejezés a tömb elemének eléréséhez szükséges indexet adja meg.

Egy tömb elemére való hivatkozás értéke a tömb elemének aktuális értéke. Például a foo[4.3] kifejezés egy hivatkozás a foo tömb `4.3' indexű elemére.

Ha egy olyan elemre hivatkozunk, aminek nincs "rögzített" értéke, akkor az értéke az üres szöveg, "", vagyis a változónak nem adtunk korábban értéket, vagy az elem törölve lett (see section A delete kifejezés). Egy ilyen hivatkozás automatikusan létrehozza az elemet egy üres szöveg értékkel. (Néhány esetben ez nem túl szerencsés, mivel ezzel awk memóriát pazarlunk el.)

Könnyen kideríthetô, hogy egy elem az adott indexszel a tömb eleme-e:

index in array

Ez a kifejezés ellenôrzi, hogy az adott index létezik-e a tömbben, és a kifejezésnek nincs mellékhatása, nem hozza létre az elemet. A kifejezésnek egy (igaz) az értéke, ha az array[index] kifejezés létezik, és zérus (hamis), ha nem létezik.

Például ha ellenôrizni akarjuk, hogy a frequencies tömbnek van-e `2'-es indexű eleme, akkor az alábbi kifejezést használhatjuk:

if (2 in frequencies)
    print "Subscript 2 is present."

Figyelem, ez a kifejezés nem azt ellenôrzi, hogy a frequencies tömbnek van-e olyan eleme aminek az értéke kettô. (Ezt csak egyféle képpen lehet kideríteni, ha az összes elemet átnézzük.) Ugyanakkor a kifejezés nem hozza létre a frequencies[2] elemet, míg a következô (helytelen) kifejezés létrehozza az elemet:

if (frequencies[2] != "")
    print "Subscript 2 is present."

Tömb elemek feltöltése

Egy tömb elemei `lvalue' kifejezések: így értéket adhatunk nekik, mint az awk változóknak:

array[subscript] = value

Itt az array a tömb neve. A subscript kifejezés a tömb elemének az indexe. A value kifejezés az az érték ami a tömb elemének az új értéke lesz.

Alapvetô példák tömbökkel

Az alábbi program egy olyan szöveget olvas be, amiben minden sor egy számmal kezdôdik, majd a számok emelkedô sorrendjében kinyomtatja a sorokat. Kezdetben a számok nincsennek sorrendben. A program sorbarendezi a sorokat. A sorszámokat tömbindexként használja. Ezután kinyomtatja a sorokat a rendezett sorrendben. Ez egy nagyon egyszerű program, hibásan működik, ha egy szám kétszer szerepel, nincs megadva az összes index, vagy egy sor nem számmal kezdôdik.

{
  if ($1 > max)
    max = $1
  arr[$1] = $0
}

END {
  for (x = 1; x <= max; x++)
    print arr[x]
}

Az elsô szabály a legnagyobb számot tárolja, illetve minden sort eltárol az arr tömbben a számmal megadott indexű elemben.

A második szabály a bemenet beolvasása után fut le, és kinyomtatja az összes sort.

Ha a programot az alábbi bemenettel futtatjuk:

5  I am the Five man
2  Who are you?  The new number two!
4  . . . And four on the floor
1  Who is number one?
3  I three you.

a kimenet az alábbi lesz:

1  Who is number one?
2  Who are you?  The new number two!
3  I three you.
4  . . . And four on the floor
5  I am the Five man

Ha egy szám többször szerepel, akkor az adott számmal kezdôdô utolsó sor lesz eltárolva a tömbben.

Az a probléma, amikor nincs megadva minden index, könnyen elkerülhetô az END szabályban:

END {
  for (x = 1; x <= max; x++)
    if (x in arr)
      print arr[x]
}

Egy tömb elemeinek ellenôrzése

Az olyan programokban, amelyekben tömböket használunk, gyakran van szükség olyan ciklusra, ami a tömb minden elemén végigmegy. Más programozási nyelvekben, ahol a tömbök folyamatosak, és az indexek pozítiv egész számok ez nagyon könnyű: az érvényes indexeket megkaphatjuk, ha elszámolunk a legalacsonyabb számtól kezdve a legmagasabbig. Ez a módszer nem működik az awk-ban, mivel bármilyen szöveg vagy szám lehet index. Így a for kifejezésnek van egy speciális alakja, amivel egy tömb összes eleme ellenôrizhetô:

for (var in array)
  body

Ez a ciklus végrehajtja a body kifejezést az array tömb minden elemére egyszer, és a var változó megkapja a korábban használt indexeket.

Alább közlünk egy programot, ami a for kifejezésnek ezt a formáját használja. Az elsô szabály végignézi a bemeneti rekordokat, és minden legalább egyszer elôforduló szót eltárol a used tömbben, ahol az adott szó szolgál indexként. A második szabály végigmegy a used tömb összes elemén, és minden 10 karakternél hosszabb szót kinyomtat, majd kinyomtatja az ilyen szavak számát. A section Szövegmanipuláló beépített függvények, további információt add a length függvényrôl.

# Minden használt szó esetén legyen az elem értéke egy.
{
    for (i = 1; i <= NF; i++)
        used[$i] = 1
}

# 10 karakternél hosszabb szavakat keresünk.
END {
    for (x in used)
        if (length(x) > 10) {
            ++num_long_words
            print x
        }
    print num_long_words, "words longer than 10 characters"
}

Lásd még a section Generating Word Usage Counts, ahol hasonló, részletesebb példák találhatók.

Egy tömb elemeinek elérési sorrendje az awk belsô struktúrájától függ, és nem lehet szabályozni vagy megváltoztatni. Ez problémákhoz vezet ha az array tömbhöz elemeket adunk a cikluson belül; nem lehet megjósólni, hogy a for ciklus eléri-e az új elemeket vagy sem. Ehhez hasonló problémák lehetnek, ha a var változót a for cikluson belül megváltoztatjuk. A legjobb az ilyen változtatásokat elkerülni.

A delete kifejezés

Egy tömb elemet el lehet távolítani, törölni lehet a delete kifejezéssel:

delete array[index]

Ha egyszer egy elem törölve lett, az értéke többé nem érhetô el. Lényegében olyan, mintha soha nem hivatkoztunk volna rá, és soha nem adtunk neki értéket.

Itt egy példa a törlésre:

for (i in frequencies)
  delete frequencies[i]

Ez a példa kitörli az összes elemet a frequencies tömbbôl.

Ha törlünk egy elemet, akkor sem egy for ciklus során nem lehet az elemet elérni, és az in operátor, ami ellenôrzi, hogy az elem létezik-e a tömbben, szintén zérussal tér vissza (pl. hamis értékkel):

delete foo[4]
if (4 in foo)
    print "Soha nem nyomtatja ezt ki"

Fontos megjegyezni, hogy egy elem törlése nem ugyanaz, mintha egy üres szöveg, "", értéket rendelünk az elemhez.

foo[4] = ""
if (4 in foo)
  print "Ezt kinyomtatja, még ha a foo[4] üres is"

Egy nem létezô elem törlése nem jelent hibát.

Egy tömb minden eleme törölhetô egy kifejezéssel, ha elhagyjuk az indexet a delete kifejezésben.

delete array

Ez a lehetôség egy gawk kiegészítés; nem érhetô el "compatibility" módban (see section Command Line Options).

Ha a delete kifejezést így használjuk, az kb. háromszor olyan hatékony, mintha az elemeket egyenként törölnénk egy ciklussal.

Az alábbi kifejezés egy hordozható, de nem magától értetôdô megoldást mutat egy tömb törlésére.

# köszönet Michael Brennan -nak ezért a példáért
split("", array)

A split függvény (see section Szövegmanipuláló beépített függvények) törli a céltömböt, mivel egy üres szöveget darabol fel. Mivel nincs mit felvágni (nincs adat), ezért egyszerűen törli a tömböt, és visszatér.

Figyelem: Egy elem törlése nem változtatja meg a típusát, így a törlés után nem használható mint skaláris szám. Például ez nem működik:

a[1] = 3; delete a; a = 3

Számok használata mint tömb index

Tömbök esetén fontos arra emlékezni, hogy a tömbök indexei mindig szövegek. Ha egy számot használunk indexként, át lesz konvertálva szöveggé mielôtt mint index lesz használva (see section Szövegek és számok konverziója).

Ez azt jelenti, hogy a CONVFMT beépített változó befolyásolja azt, hogy a tömbök elemeit hogyan lehet elérni. Például:

xyz = 12.153
data[xyz] = 1
CONVFMT = "%2.2f"
if (xyz in data)
    printf "%s is in data\n", xyz
else
    printf "%s is not in data\n", xyz

Ez kinyomtatja: `12.15 is not in data'. Az elsô kifejezés numerikus értéket ad az xyz változónak. Ezután a data[xyz] kifejezésben lényegében az index a "12.153" szöveg lesz (az alap konverziót a CONVFMT alapján végzi el, "%.6g"), és egyet rendel a data["12.153"] tömb elemhez. A program ezután megváltoztatja a CONVFMT értékét. A `xyz in data' kifejezés egy új szöveget generál a xyz változóból, ez alkalommal "12.15", mivel a CONVFMT csak két értékes jegyet enged meg. A teszt nem lesz sikeres, mivel a "12.15" szöveg nem egyezik meg a "12.153" szöveggel.

A konverzió szabályai szerint (see section Szövegek és számok konverziója), az egész számokat mindig mint egész szám konvertálja szöveggé, attól függetlenül, hogy mi a CONVFMT értéke. Így:

for (i = 1; i <= maxsub; i++)
    valami történik itt az array[i] -vel

a program mindig működik, attól függetlenül hogy mi a CONVFMT értéke.

Mint a legtöbb dolog az awk-ban, a legtöbb esetben úgy működik, ahogy azt elvárjuk. De fontos, hogy pontosan tudjuk, hogy milyen szabályok érvényesek, mivel gyakran egy apró részlet nagy hatással lehet a programodra.

Nem inicializált változók használata mint index

Tegyük fel, hogy a bemeneti sorokat fordított sorrendben akarjuk kinyomtatni. Egy ésszerű kísérlet ennek megvalósítására az alábbi program, ami valahogy így nézne ki:

$ echo 'line 1
> line 2
> line 3' | awk '{ l[lines] = $0; ++lines }
> END {
>     for (i = lines-1; i >= 0; --i)
>        print l[i]
> }'
-| line 3
-| line 2

Sajnos a legelsô bemeneti sort nem nyomtatta ki a program!

Elsô pillantásra a programnak működnie kellene. A lines változó nincs inicializálva, és egy nem inicializált változónak zérus szám értéke van. Így az l[0] értéket kellene kinyomtatnia.

De a lényeg, hogy az awk tömbök indexei mindig szövegek. Egy nem inicializált változó, amikor szöveg az értéke "" és nem zérus. Így a `line 1' az l[""] -ben lesz tárolva.

A program alábbi verziója helyesen működik:

{ l[lines++] = $0 }
END {
    for (i = lines - 1; i >= 0; --i)
       print l[i]
}

Itt a `++' operátor arra kényszeríti a lines változót, hogy szám típusú legyen, így a régi érték numerikus zérus lesz, ami "0" szöveggé lesz konvertálva mint tömb index.

Amint az láttuk, bár egy kicsit szokatlan, de az üres szöveg ("") is használható mint tömb index (s.s.). Ha a `--lint' opció szerepel a parancssorban (see section Command Line Options), a gawk figyelmeztet arra, hogy egy üres szöveget akartunk indexként használni.

Többdimenziós tömbök

Egy többdimenziós tömb egy olyan tömb, amiben az elemeket indexek sorozata azonosít, és nem csak egy index. Például egy kétdimenziós tömb esetén két indexre van szükség. Általában (más programozási nyelvekben, beleértve az awk-ot is) egy kétdimenziós tömböt mátrixnak is szoktak nevezni, pl. matrix[x,y].

Az awk támogatja a többdimenziós tömbök használatát, indexek egy szövegbe kapcsolásával. Lényegében az történik, hogy az awk az indexeket szöveggé alakítja (see section Szövegek és számok konverziója), és összekapcsolja ôket egy speciális elválasztóval. Így egyetlen szöveget kapunk. Ezt a kombinált szöveget használja mint egy index egy egydimenziós tömbben. Az elválasztót a SUBSEP beépített változó értéke adja meg.

Például tegyük fel, hogy a `foo[5,12] = "value"' kifejezést kiértékeljük, amikor a SUBSEP változó értéke "@". Az ötös és a 12-es számot átalakítja szövegekké és összekapcsolja ôket egy `@' jellel közöttük, aminek az eredménye: "5@12". Így a foo["5@12"] tömb eleme "value" lesz.

Ha egyszer egy tömb elemének értéket adtunk, attól kezdve nincs arra lehetôség, hogy kiderítsük, hogy egy indexet vagy több indexet használtunk. A `foo[5,12]' és a `foo[5 SUBSEP 12]' kifejezések mindig teljesen azonosak.

A SUBSEP alapértéke a "\034" szöveg, ami egy nem nyomtatható karakter, és így nem valószínű, hogy egy awk programban vagy adatfile-ban elôfordulhat.

Egy valószínűtlen karakter használatának hasznossága abból fakad, hogy egy index értéke ami tartalmazza a SUPSEP szöveget olyan kombinált szöveghez vezet ami kétértelmű. Tegyük fel, hogy a SUBSEP értéke "@", akkor a `foo["a@b", "c"]' és a `foo["a", "b@c"]' kifejezések teljesen megkülönböztethetetlenek mivel végül is a `foo["a@b@c"]' kifejezéssel egyeznek meg.

Ugyanazzal az `in' operátorral lehet ellenôrizni egy index létezését egy többdimenziós tömbben, mint egy egyszerű egydimenziós tömbben. Egyetlen index helyett indexek sorozatát kell leírni, vesszôvel elválasztva zárójelekben:

(subscript1, subscript2, ...) in array

A következô példa a bemenetet mint egy két dimenziós tömb kezeli; elforgatja ezt a tömböt 90 fokkal az óramutató járásával egyezô irányban, és kinyomtatja az eredményt. A program feltételezi, hogy minden sor azonos számú elemet tartalmaz.

awk '{
     if (max_nf < NF)
          max_nf = NF
     max_nr = NR
     for (x = 1; x <= NF; x++)
          vector[x, NR] = $x
}

END {
     for (x = 1; x <= max_nf; x++) {
          for (y = max_nr; y >= 1; --y)
               printf("%s ", vector[x, y])
          printf("\n")
     }
}'

Amikor az alábbi az input:

1 2 3 4 5 6
2 3 4 5 6 1
3 4 5 6 1 2
4 5 6 1 2 3

a végeredmény:

4 3 2 1
5 4 3 2
6 5 4 3
1 6 5 4
2 1 6 5
3 2 1 6

Többdimenziós tömbök elérése

Nincs speciális for kifejezés "többdimenziós" tömbök eléréséhez; nem is lehet, hiszen igazából nincsenek is többdimenziós tömbök; csak többdimenziós tömb elérési mód van.

Ugyanakkor, ha a programod mindig mint több dimenziós tömb használja a tömböt, akkor azt a hatást kelthetjük ha a for kifejezést (see section Egy tömb elemeinek ellenôrzése) kombináljuk a split beépített függvénnyel (see section Szövegmanipuláló beépített függvények). Így működik:

for (combined in array) {
  split(combined, separate, SUBSEP)
  ...
}

Ez beállítja a combined változót minden összefűzôtt indexre, és feldarabolja egyedi indexekre, ott ahol a SUBSEP értékének megfelelô szöveg található. A feldarabolt indexek a separate tömb elemei lesznek.

Így, ha korábban egy értéket tároltunk az array[1, "foo"] -ban, akkor létezik egy elem az array tömbben "1\034foo" indexel. (A SUBSEP alapértéke egy 034 -es kódú karakter.) Elôbb vagy utóbb a for ciklus megtalálja ezt az elemet, és combined értéke a "1\034foo" lesz. Ezután a split függvényt hívja meg:

split("1\034foo", separate, "\034")

Ennek az eredménye, hogy a separate[1] értéke "1" lesz és a separate[2] értéke "foo". Vagyis visszakaptuk az eredeti indexsorozatot.


Go to the first, previous, next, last section, table of contents.