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.