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.
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 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."
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.
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] }
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.
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
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.
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.
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
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.