A tipikus awk programokban a bemenet vagy a standard bemenet (általában a billentyűzet, de gyakran egy cső (pipe) valamilyen másik parancsból), vagy file-ok, amelyek nevét az awk parancssorában kell megadni. Az awk a bemeneti file-okat sorrendben olvassa be, és előbb befejezi az egyiket, mielőtt elkezdené a következőt. Az éppen aktuális file nevét a FILENAME beépített változóból lehet kiolvasni.
Az awk a bemenetet rekordokban olvassa be és dolgozza fel a programodban megadott szabályok szerint, egyszerre csak egyet. Alapesetben egy rekord egy sornak felel meg. Ezenkívül automatikusan minden rekordot feldarabol úgynevezett mezőkre, ezzel kényelmesebbé téve a rekord részeinek elérését a programod számára.
Ritkán, de előfordulhat, hogy a getline parancsot kell használnod. A getline parancs nagyon fontos része a nyelvnek, mivel adatot tud beolvasni bármilyen és bármennyi file-ból, ráadásul ezeket a file-okat nem kötelező megadni az awk parancssorában.
Az awk segédprogram a bemenetet rekordokra és mezőkre darabolja fel a programod számára. A rekordok egy úgynevezett rekordelválasztóval vannak elválasztva egymástól. Alapesetben a rekordelválasztó az új sor karakter. Ezért van az, hogy alapesetben egy sor megfelel egy rekordnak. Más karakter is megadható mint rekordelválasztó, ha az RS beépített változót beállítjuk a kívánt karakterre.
Az RS értékének megváltoztatása ugyanúgy történik mint bármilyen más változóé, az értékadó operátorral, `='. Az új rekordelválasztót idézőjelek közé kell tenni, mint egy szöveg konstanst. Általában az értékadást a legjobb azelőtt végrehajtani mielőtt bármilyen adatot a program feldolgozna, így minden adat a megfelelő elválasztó karakterrel lesz kezelve. Ehhez a BEGIN szabályt kell használni. Például:
megváltoztatja az RS értékét a "/" karakterre mielőtt bármilyen adatot beolvasna, így a rekordelválasztó karakter a "/" lesz. Ezután elkezdi olvasni a file tartalmát és az awk program második szabályát alkalmazza (tevékenységet minta nélkül), ami kinyomtat minden rekordot. Mivel a print kifejezés minden kinyomtatott rekordhoz egy új sort ad, a program lényegében a bemenetet a kimenetre másolja át úgy, mintha minden "/" karaktert lecserélnénk egy új sor karakterre. Az eredmény a `BBS-list' file-al:
Érdemes megfigyelni, hogy a `camelot' kezdetű sor nem lett feldarabolva. Az eredeti file-ban a sor így néz ki:
Csak egyetlen baud érték van megadva és nincs "/" karakter a sorban.
Egy másik lehetőség a rekordelválasztó megváltoztatására a parancssor használata.
Ez a parancssor beállítja az RS változót a `/' karakterre, mielőtt elkezdené a `BBS-list' file feldolgozását.
A speciális karakter, mint a `/' karakter használata az esetek legnagyobb részében nem okoz problémát, de a következő speciális parancssor csak egy meglepő `1'-est nyomtat ki. Az NF beépített változó értéke az aktuális rekordon belüli mezők számát adja meg. Jelen esetben egyetlen mező van, ami az új sor karaktert tartalmazza.
Amint eléri a bemenet végét a jelenlegi rekordot lezárja, még akkor is ha az utolsó karakter a file-ban nem a rekordelválasztó volt (s.s.).
Az üres szövegnek, "" (szöveg, amiben nincs karakter), mint az RS értéke, speciális jelentése van: azt jelenti, hogy a rekordok egy vagy több üres sorral vannak elválasztva és semmi mással.
Ha az RS értékét az awk futása közben változtatod meg, akkor az új értéket csak az új rekord beolvasásától kezdve veszi figyelembe, az éppen aktuális (és az előzőleg feldolgozott) rekordokat nem befolyásolja.
Miután megtalálta a rekord végét, a gawk az RT változót beállítja arra a karakterre/szövegre, ami illeszkedett az RS rekordelválasztóra.
Valójában az RS értéke nem csak egy karakter lehet, hanem bármilyen reguláris kifejezés. Általában egy rekord a megadott reguláris kifejezés kezdeténél végződik; a következő rekord az illeszkedő reguláris kifejezés végénél kezdődik. Ez a szabály érvényesül alapesetben is, amikor az RS az új sor karakter: a rekord a következő illeszkedő reguláris kifejezésnél ér véget (az új sor karakternél), a következő rekord pedig a reguláris kifejezés végénél kezdődik (vagyis a következő sor első karakterénél). Mivel az új sor karakter illeszkedik az RS-re ezért egyik rekordnak sem része.
Amikor az RS értéke csak egy karakter, akkor az RT is ugyanazt a karaktert fogja tartalmazni. Ugyanakkor, ha az RS értéke egy reguláris kifejezés az RT sokkal hasznosabb lehet; azt az aktuális szövegrészletet tartalmazza, ami illeszkedett a reguláris kifejezésre.
A fentieket az alábbi példa illusztrálja, ahol az RS egy olyan reguláris kifejezés amelyik vagy az új sor karakterre vagy egy olyan szövegre illeszkedik, amely egy vagy több nagybetűt tartalmaz, ill. előtte és/vagy mögötte egy szóköz karakter lehet.
Az utolsó sor egy üres sor. Ez azért van, mert az utolsó RT értéke egy új sor és a print kifejezés mindig hozzáad egy lezáró új sor karaktert a kimenethez.
Az RS használata mint reguláris kifejezés és az RT változó az awk nyelv gawk kiegészítései; "compatibility" módban nem használhatók. "Compatibility" módban az RS-nek csak az első karakterét használja a rekord végének megállapítására.
Az awk segédprogram azt is számontartja, hogy eddig hány darab rekordot olvasott be az aktuális bemeneti file-ból. Ezt az értéket az FNR beépített változóban lehet megtalálni. Amikor egy új file-t kezd el olvasni a változó értéke mindig lenullázódik. Egy másik beépített változó, az NR, az összes file-ból az összes eddig beolvasott rekordok számát tárolja. Kezdeti értéke zérus és soha nem nullázódik le automatikusan.
A beolvasott rekordot az awk automatikusan feldarabolja mezőkre. Alapesetben a mezőket szóközök vagy tab vagy új sor karakterek választják el, mint a szavakat egy mondatban; hasonló karakterek, mint lapdobás (formfeed) nem szolgálnak elválasztóként az awk-ban.
A mezők célja, hogy kényelmesebbé tegyék számodra a rekordok feldolgozását. Nem kötelező őket használni -- dolgozhatsz csak a rekorddal -- de a mezők teszik az awk programokat igazán hasznossá.
Az awk programban egy mezőre egy dollár jellel, `$', és az utána következő számmal lehet hivatkozni. Így, a $1 az első, a $2 a második mezőre utal. Vegyük a következő sort:
Itt az első mező vagy $1 a `This'; a második mező vagy $2 a `seems' és így tovább. Az utolsó mező vagy $7 az `example.'. Mivel nincs szóköz a utolsó `e' betű és a lezáró `.' pont között ezért a pont a mező része lesz.
Az NF beépített változó adja meg, hogy az aktuális rekordban hány mező van. Az awk automatikusan frissíti az NF értékét minden új rekord beolvasásánál.
Akárhány mező van a rekordban, az utolsó rekordra a $NF-el is lehet hivatkozni. Így a fenti példában az $NF ugyanaz lenne mint a $7, ami az `example.'. Hogy ez miért működik, azt egy kicsit később magyarázzuk el. Ha az utolsó utáni mezőre hivatkozol, például a $8-ra amikor csak hét mező van a rekordban, egy üres szöveget kapsz eredményül.
A $0 egy speciális eset, a teljes rekordot reprezentálja. $0-t használhatod ha a mezőkkel nem akarsz foglalkozni.
Még egy példa:
Ez a példa minden olyan rekordot kinyomtat a `BBS-list' file-ból, amelynek az első mezőjében előfordul a `foo' szó. A `~' operátor az illesztő operátor; azt ellenőrzi, hogy a megadott kifejezés (itt a $1 mező) illeszkedik-e a reguláris kifejezésre.
Ezzel ellentétben, a következő példa a `foo' szót keresi a teljes rekordban és csak az első és az utolsó mezőt nyomtatja ki az illeszkedő rekordoknál.
A mezőazonosító szám nem csak konstans lehet. Az awk nyelvben a `$' karakter után bármilyen kifejezés állhat. A kifejezés értéke adja meg a mező számát. Ha kifejezés értéke szöveg, akkor azt átalakítja számmá. Például:
Ha emlékszem, akkor az NR az eddig beolvasott rekordok számát tartalmazza; az első rekordnál egy, a másodiknál kettő, és így tovább, az értéke. Így ez a példa kinyomtatja az első mezőt az első rekordnál, a második mezőt a második rekordnál, és így tovább. A huszadik rekordnál a huszadik mezőt nyomtatja ki; de mivel valószínűleg a rekordban nincs 20 mező ezért csak egy üres sort fog kinyomtatni.
Itt egy másik példa, ahol egy kifejezést használunk a mezőazonosító számnak:
Az awk először kiértékeli a `(2*2)' kifejezést, majd az eredményül kapott számot használja a mező azonosítására. A `*' jel szorzást jelent, így a `2*2' kifejezés értéke négy lesz. A zárójelek azért kellenek, hogy előbb a szorzás hajtódjon végre és csak utána a mező azonosítás; zárójelek mindig kellenek, ha matematikai műveletet használunk a mezőazonosító szám előállítására. Végül is ez a példa a `BBS-list' file minden sorából a negyedik mezőt fogja kinyomtatni.
Ha a mezőazonosító szám a kiértékelés után zérus lesz, akkor a teljes rekordot kapod eredményül. Így a $(2-2) kifejezés ugyanaz mint a $0. Negatív számok nem megengedettek mezőazonosítóként; ha mégis előfordulna, akkor valószínűleg az awk program leáll. (A POSIX szabvány nem definiálja a viselkedést negatív szám esetére. A gawk leállítja a programot negatív szám esetén, más awk implementációk másképp viselkedhetnek.)
Ahogy azt korábban elmondtuk, az NF beépített változó a mezők számát adja meg az aktuális rekordban. Így a $NF kifejezés nem egy speciális kifejezés, csak a közvetlen következménye az NF használatának, mint mezőazonosító szám.
Egy mező tartalmát meg is változtathatod a programon belül. (Bár ez a bemenetet megváltoztatja az awk számára, valójában a tényleges bemenet változatlan; az awk soha nem módosítja a bemeneti file-t.)
Vegyük a következő példát és a kimenetét:
A `-' jel a kivonás, így ez a program a harmadik mezőnek, $3, új értéket ad, a második mezőből tizet vonva ki, `$2 - 10'. Ezután a második és a harmadik mezőt kinyomtatja az új értékkel.
Ahhoz, hogy ez működjön, a második mezőnek, $2, olyan szöveget kell tartalmaznia, ami egy értelmes szám; a szöveget átalakítja számmá, hogy a matematikai műveletet elvégezhesse. A kivonás eredményét átalakítja szöveggé, amit végül hozzárendel a harmadik mezőhöz.
Amikor egy mező tartalmát megváltoztatod, az awk a rekord szövegét újra kiértékeli, hogy a rekord tükrözze a változást. Így a $0 a megváltozott mezőt fogja tartalmazni. A következő program a bemeneti file-t nyomtatja ki úgy, hogy minden sorban a második mező értékéből tizet kivon.
Olyan mezőkhöz is rendelhető tartalom, amelyek nem részei a rekordnak. Például:
A $6 nem létezik a rekordban, mi hoztuk létre és a $2, $3, $4 és a $5 mezők tartalmának összegével töltöttük fel. A `+' jel összeadást jelent. Az `inventory-shipped' file esetén a $6 mező az egy hónapban elküldött összes csomag számát jelenti.
Egy új mező létrehozása esetén, a bemeneti rekord belső reprezentációja is megváltozik -- a $0 értéke. Így a mező létrehozása után egy `print $0' kifejezés kinyomtatja a rekordot az új mezővel együtt, a megfelelő mezőelválasztót használva.
A rekord új kiértékelése befolyásolni fogja az NF értékét (a mezők száma), ugyanakkor az NF és az eddig nem tárgyalt kimeneti mezőelválasztó, OFS is befolyásolva lesz a kiértékelés által. Például az általad létrehozott legnagyobb mezőazonosító számot fogja az NF tartalmazni.
Ugyanakkor csak egy egyszerű hivatkozás egy olyan mezőre, ami nem szerepel a rekordban, nem fogja megváltoztatni sem a $0 sem az NF értékét. A hivatkozás egy üres szöveget fog generálni, például:
program részlet az `everything is normal' szöveget fogja kinyomtatni, mivel a NF+1-ik mező természetesen nem szerepel a rekordban.
Fontos megjegyezni, hogy egy létező mező tartalmának megváltoztatása befolyásolni fogja a $0 értékét, de nem fogja megváltoztatni NF értékét, még akkor sem ha a mező új értéké az üres szöveg. Például:
A mező még mindig ott van; csak éppen üres, amit a két egymást követő kettőspont is jelez.
Ez a példa bemutatja, hogy mi történik, ha egy új mezőt hozunk létre.
A közbenső, $5 mező is létrejön egy üres szöveggel (a második dupla kettőspont mutatja) és az NF értékét hatra állítja.
Végül, ha az NF értékét csökkentjük, akkor mező(ke)t dobunk el és a $0 új értéket kap a kiértékelés után. Például:
A mezőelválasztó, vagy egy karakter vagy egy reguláris kifejezés, adja meg, hogy az awk hogyan darabolja fel a bemeneti rekordot mezőkre. Az awk a mezőelválasztóra illeszkedő karaktersorozatokat keres a bemeneti rekordban és a mezők azok a szövegek lesznek amelyek az illeszkedő részek között helyezkednek el.
Az alábbi példákban a szóköz helyett a "*" jelet fogjuk használni a kimenetben.
Ha a mezőelválasztó az `oo', akkor az alábbi sor:
az `m', `*g' és a `*gai*pan' mezőkre lesz feldarabolva. A második és a harmadik mező előtti szóköz is a mező része lesz.
A mezőelválasztót az FS beépített változó tartalmazza. Shell programozók figyelem! Az awk nem használja az IFS nevet, amit a POSIX szabványos shell-ek használnak (Bourne shell, sh, vagy a GNU Bourne-Again Shell, Bash).
Egy awk programon belül az FS értékét az `=' értékadó operátorral változtathatod meg. A legjobb alkalom erre a program eleje, mielőtt bármilyen adatot a program beolvasna, így a legelső rekord is a megfelelő mezőelválasztóval lesz feldolgozva. Például a BEGIN minta használatával teheted ezt meg. Az alábbi példában az FS értéke a "," lesz:
és ha a bemeneti sor:
akkor az awk program a `*29*Oak*St.' szöveget fogja kinyomtatni.
Előfordul, hogy az adatod olyan helyen is tartalmaz elválasztó karaktert, ahol azt nem várnád. Például személy nevek esetén a fenti példa sorban tudományos cím vagy egyéb adat is megadható, mint `John Q. Smith, LXIX'. Tehát:
sor esetén a program a `*LXIX'-et fogja kinyomtatni és nem a `*29*Oak*St.'. Ha a lakcímeket szeretted volna kigyűjteni, természetesen meglepődnél. A tanulság az, hogy az adatstruktúrát és az elválasztó karaktereket gondosan kell megválasztani, hogy az ilyen problémák elkerülhetők legyenek.
Amint azt tudod, alapesetben a mezőket szóközök, tab és új sor karakterek választják el egymástól; nem csak egy szóköz: két szóköz egymás után nem generál egy üres mezőt. Az FS alapértéke a " ", egy szóközt tartalmazó szöveg. Ha ezt a normális módon értelmeznénk, minden szóköz egy mezőt választana el, így két szóköz egymás után egy üres mezőt generálna. Az ok amiért nem ez történik az, hogy egyetlen szóköz mint az FS értéke egy speciális eset.
Ha az FS bármilyen más karaktert tartalmaz, pl. a ",", akkor a karakter minden előfordulása két mezőt választ el egymástól. Két egymás utáni megjelenése egy üres mezőt határol. Ha egy rekord elején vagy végén fordul elő, az is üres mezőt jelent. A szóköz karakter az egyetlen kivétel, ami nem követi ezt a szabályt.
Az előző alfejezet bemutatta egy karakter használatát mint mezőelválasztó. Általánosítva, az FS értéke bármilyen reguláris kifejezés lehet. Ebben az esetben, minden illeszkedés a rekordon belül két mezőt választ el egymástól. Például:
esetén minden olyan szöveg, ami egy vesszőből, egy szóköz és egy tab karakterből áll, elválaszt két mezőt. (`\t' egy escape szekvencia, ami a tab karaktert helyettesíti.)
Egy kicsit bonyolultabb példa: tegyük fel, hogy a szóköz karaktert ugyanúgy szeretnéd használni, mint más karaktereket a mezők elválasztására. Ebben az esetben az FS-t a "[ ]" reguláris kifejezésre kell beállítani (nyitó szögletes zárójel, szóköz, záró szögletes zárójel). Ez a reguláris kifejezés csak egy szóközre illeszkedik és semmi másra.
Van egy fontos különbség a `FS = " "' és a `FS = "[ \t\n]+"' kifejezések között. Mindkét esetben a mezőket egy vagy több szóköz, tab és/vagy új sor karakter választja el, de amikor az FS értéke a " ", az awk először levágja a kezdő és záró szóközöket, tab és új sor karaktereket és csak utána kezdi el feldolgozni a rekordot.
Az alábbi példa csak egy `b'-t fog kinyomtatni:
de ez egy `a'-t nyomtat ki (extra szóközök vannak a betűk körül):
Ebben az esetben az első mező üres.
A kezdő és záró szóköz, tab és új sor karakterek levágása a $0 új kiértékelésénél is fontos szerepet játszik, így:
Az első print kifejezés kinyomtatja az eredeti rekordot. A $2 mező értékadása után újraértékeli a $0 tartalmát; a $1-tól az $NF-ig a mezőket összefűzi az OFS tartalmát használva elválasztásra. Mivel a kezdő és záró szóköz karaktereket nem vette figyelembe a $1 megállapításánál, így azok most sem részei az új $0-nak. Végül az utolsó print kifejezés kiírja az $0 új tartalmát.
Előfordulhat, hogy egy rekord minden karakterét meg szeretnéd vizsgálni külön-külön. gawk-ban ez egyszerű, egy üres szöveget ("") kell az FS-hez rendelni. Ebben az esetben minden karakter egy önálló mező lesz:
Hagyományosan, ha az FS értéke "", akkor az awk viselkedése nem definiált. Ebben az esetben a Unix awk az egész rekordot egy mezőnek tekinti (s.s.). "Compatibility" módban, ha az FS értéke "" a gawk is így viselkedik.
Az FS értéke a parancssorból is beállítható, a `-F' opció használatával:
hatására az FS értéke a `,' karakter lesz. Fontos észrevenni, hogy az opciót a nagy `F' betűvel lehet megadni, míg a kis `f' betű az awk programot tartalmazó file-t adja meg. A `-F' és `-f' -nek semmi köze egymáshoz, a kis- és nagybetű megkülönböztetés fontos. Természetesen a két opciót használhatod egyszerre.
A `-F' utáni argumentum feldolgozása ugyanúgy történik mintha az FS-t a programban állítanánk be. Ez azt jelenti, hogy ha a mezőelválasztó egy speciális karaktert tartalmaz, akkor azt megfelelően védeni kell a `\' karakterrel. Például ha a mezőelválasztó egy `\' karakter, akkor ezt kell begépelni:
Mivel a `\' karakter a shell számára is speciális karakter, ezért az awk csak a `-F\\' kifejezést fogja megkapni. Ezután az awk feldolgozza a `\\' escape szekvenciát, ami végül a `\' jelet adja meg mint mezőelválasztó.
Egy speciális eset, ha "compatibility" módban az `-F' után csak egy `t' betű áll. Ekkor az FS valójában a tab karaktert kapja értékül. Ez azért van, mert ha a `-F\t' gépeled be idézőjelek nélkül, akkor a `\' jelet a shell eldobja és az awk azt gondolja, hogy a tab karaktert akartad megadni és nem a `t' betűt, mint mezőelválasztó. Ha tényleg csak a `t' betűvel akarod elválasztani a mezőket, akkor a `-v FS="t"' kifejezést kell használni.
Például készítsünk egy `baud.awk' nevű file-t, ami a /300/ mintát és a `print $1' tevékenységet tartalmazza:
Állítsuk be az FS-t a `-' karakterre, majd futtassuk a programot a `BBS-list' file-al. Az alábbi parancs kilistázza azoknak a gépeknek a nevét és a telefonszámuk első három jegyét, amelyek 300 baud-al működnek:
A második sor nem egészen tökéletes. Az eredeti file-ban így nézett ki:
A `-' karakter szerepel a rendszer nevében, így nem a telefonszámot írja ki, ahogy azt szeretnénk. Ez is mutatja mennyire fontos, hogy gondosan válasszuk meg a mezőket és a mezőelválasztókat.
A Unix rendszereken a jelszó (passwd) file-ban minden felhasználóhoz tartozik egy bejegyzés (egy sor). A mezők kettősponttal vannak elválasztva. Az első mező a bejelentkezési név, a második a felhasználó jelszava. (A legtöbb rendszeren ma már nem elérhető a jelszó a felhasználók számára.) Egy bejegyzés így nézhet ki:
Az alábbi program végignézi a jelszó file-t és kilistázza azokat a felhasználókat akiknél nincs jelszó megadva:
A POSIX szabvány szerint az awk-nak úgy kell viselkednie, mintha minden rekordot a beolvasás során darabolna fel mezőkre. Ez azt jelenti, hogy ha megváltoztatod az FS értékét miután a rekordot beolvasta, akkor a mezők feldarabolása azt az állapotot kell tükrözze, ami a régi FS használatával érvényes.
Ugyanakkor sok awk implementáció nem így működik. Ezek a programok csak akkor darabolják fel a a rekordot mezőkre, amikor hivatkoznak egy mezőre, így a mezők az éppen aktuális FS mezőelválasztó szerint lesznek megállapítva. (s.s.) Ezt a viselkedést nehéz felfedezni. Az alábbi program illusztrálja a különbséget a két megoldás között. (A sed parancs a `/etc/passwd' file-nak csak az első sorát nyomtatja ki.)
Egy rossz awk implementáció esetén a program a
sort nyomtatja ki, míg a gawk valami ehhez hasonlót fog kiírni:
Az alábbi táblázat összefoglalja, hogy az FS értékétől függően a mezők hogyan lesznek elválasztva . (A `==' jelentése egyenlő.)
A gawk 2.13-as verziója vezette be azt a megoldást, amivel adott szélességű mezőket lehet kezelni, és nincs mezőelválasztó. Régi FORTRAN programok bemeneteként fordulhat elő ilyen adat, ahol a számok között nincs elválasztás.
Lényegében egy olyan táblázatról beszélünk, ahol az oszlopok szóközökkel vannak igazítva és az üres mező csak egy szóköz. Ilyen környezetben az awk mezőelválasztó stratégiája nem igazán tökéletes. Habár egy hordozható program a substr függvény használatával meg tudja oldani a problémát, a megoldás nem túl szép és különösen nem lenne hatékony sok rekord esetén.
A rekord adott szélességű mezőkre darabolásához a FIELDWIDTHS beépített változónak kell egy olyan szöveget megadni, ahol a szövegben a mezők szélességét jelentő számokat szóközök választanak el. Minden szám egy mező szélességét adja meg, a mezők közti szóközöket is beleszámolva. Ha bizonyos oszlopokkal nem akarsz foglalkozni, akkor definiáld egy külön mezőbe a szélesség megadásával, majd ne vedd figyelembe a keletkezett mezőt.
Az alábbi adatsor a w Unix segédprogram kimenete és alkalmas a FIELDWIDTHS használatának bemutatására.
Az alábbi program a fenti adatból kiválogatja az üresjárati (idle) időtartam hosszát, átkonvertálja másodpercbe, majd kiírja az első két mezőt és az üresjárati időt másodpercben. (A program olyan megoldásokat is tartalmaz, amiket eddig nem tárgyaltunk.)
Az eredmény:
Egy másik (talán praktikusabb) példa a szavazókártyák feldolgozása. Az USA egyes területein úgy kell szavazni, hogy lyukat kell ütni egy kártyába. Ezeket a kártyákat használjak a szavazatszámlálás során. Mivel az is előfordulhat, hogy valaki az adott kérdésben nem akar szavazni egyes oszlopok üresek lehetnek. Az ilyen adatok feldolgozására az gawk jól használhatná a FIELDWIDTHS megoldást. (Persze az egy másik kérdés, hogy a gawk hogyan kerülne a kártyaolvasó gépbe!)
Ha értéket adunk az FS változónak a gawk visszatér az eredeti mező darabolási metódushoz. Mivel valószínűleg nem akarod tudni az FS értékét, csak visszakapcsolni normál módba, használhatod a `FS = FS' kifejezést is.
Ez a lehetőség még csak kísérleti, idővel változhat. Figyelj oda, mert a gawk egyáltalán nem ellenőrzi a FIELDWIDTHS-nek megadott értékeket.
Ha egy adatbázisban egy sor nem tudja kényelmesen tárolni az összes információt, akkor több soros rekordot érdemes használni.
Az első lépés a megfelelő adatformátum kiválasztása: hogyan definiálsz egy rekordot? Mi választja el a rekordokat?
Az egyik megoldás valamilyen szokatlan karakter vagy szöveg használata rekordelválasztóként. Például használhatod a lapdobás (formfeed) karaktert (`\f' az awk-ban mint a C programozási nyelvben is), így minden rekord egy oldal a file-ban. Ehhez csak az RS változót kell az "\f"-re beállítani (egy olyan szövegre, ami csak a a lapdobás karaktert tartalmazza). Bármilyen más karakter is megfelelő, ha biztos vagy benne, hogy nem fog a rekordon belül előfordulni.
A másik megoldás, hogy üres sorok választják el a rekordokat. Ha az RS értéke egy üres szöveg, akkor a rekordokat egy vagy több üres sor választhatja el. Ebben az esetben a rekord mindig az első üres sornál ér véget, és a következő rekord az első nem üres sornál kezdődik - nem számít, hogy hány üres sor van a két rekord között, mindig egy elválasztóként lesznek kezelve.
Ugyanezt a hatást érheted el az "\n\n+" kifejezés használatával. Ez a reguláris kifejezés illeszkedik a rekord utáni új sorra és a követő egy vagy több üres sorra. Ráadásul a reguláris kifejezések a lehető leghosszabb mintára illeszkednek, így a következő rekord csak az üres sorok után kezdődik - nem számít, hogy hány üres sor van a két rekord között, mindig egy elválasztóként lesznek kezelve.
Van egy fontos különbség a `RS = ""' és a `RS = "\n\n+"' kifejezések között. Az első esetben az adat file-ban előforduló kezdő és záró üres sorokat nem veszi figyelembe, egyszerűen eldobja azokat. A második esetben ez nem történik meg. (s.s.)
Most, hogy a bemenetet feldaraboltuk több sorból álló rekordokra, a második lépés a rekordokon belüli mezők megállapítása. Az egyik megoldás, hogy a sorokat hagyományos módon feldaraboljuk mezőkre, de amikor az RS értéke egy üres szöveg az új sor karakter mindig mezőelválasztóként viselkedik. Ez csak egy ráadás az FS-ben megadott elválasztóhoz.
Ugyanakkor ez probléma is lehet, ha az új sor karaktert nem akarod mezőelválasztóként használni. Mivel ezt a speciális beállítást kikapcsolni nem lehet, csak a split függvény használatával tudod megfelelően feldarabolni a rekordot.
Egy másik megoldás a rekordok feldarabolására, hogy minden mezőt külön sorba teszünk: ekkor az FS-t a "\n" szövegre kell beállítani. (Ez az egyszerű reguláris kifejezés csak egy új sor karakterre illeszkedik.)
Egy praktikus példa az így elrendezett adatokra egy levelezési címeket tartalmazó lista, ahol minden bejegyzést egy üres sor választ el. Egy ilyen file, `addresses', így nézhet ki:
Egy egyszerű program a file feldolgozására:
A programot futtatva ezt az eredményt kapjuk:
Az alábbi lista összefoglalja, hogy a rekordok hogyan lesznek feldarabolva az RS értékétől függően (a `==' egyenlőséget jelent):
A gawk mindig beállítja az RT változót az RS-re illeszkedő szövegre.
Eddig a bemenetet az awk program számára vagy a szabványos bemenetről (általában a terminálról, néha egy másik program kimenetéből) vagy a parancssorban megadott file-okból olvastuk be. Az awk nyelvben a getline beépített függvénnyel lehet explicit módon a bemenet olvasását irányítani.
A getline függvény hasznos lehet sokféleképpen, de kezdő felhasználóknak nem ajánlott. Azért itt tárgyaljuk, mivel minden a bemenettel kapcsolatos dolgot ebben a fejezetben tárgyalunk. A getline függvény bemutatására használt példákban előfordul olyan programozási megoldás, amit eddig még nem tárgyaltunk, így ezt a részt ajánlott újra elolvasni miután végigolvastad, és már jól ismered a könyvet.
A getline visszatérési értéke egy, ha sikeresen beolvasott egy rekordot és zérus ha elérte a bemenet végét. Ha valami hiba volt az olvasás során, például a file nem érhető el, akkor a visszatérési értéke -1. Ebben az esetben a gawk az ERRNO változót beállítja egy, a hibát leíró szövegre.
Az alábbi példákban a command egy shell parancsot helyettesít.
Az argumentum nélkül meghívott getline függvény az aktuális file-ból olvas be rekordot. Csak annyit csinál, hogy beolvassa a következő rekordot és feldarabolja mezőkre. Ez a viselkedés akkor lehet hasznos, ha befejezted az aktuális rekord feldolgozását és közvetlenül utána valamilyen speciális műveletet akarsz a következő rekordon elvégezni. Itt egy példa:
Ez az program kitöröl minden a C programozási nyelvben szokásos megjegyzést, `/* ... */', a bemenetből. Ha a `print $0' kifejezést lecseréled valamilyen másik kifejezésre, akkor bonyolultabb műveletet is végezhetsz a megjegyzésektől mentes bemeneten, pl. reguláris kifejezésekkel változókat, stb. kereshetsz. A programnak van egy apró hibája -- ugyanis nem működik, ha egy megjegyzés az adott sorban végződik és egy másik megjegyzés ugyanabban a sorban kezdődik.
Ha a getline függvényt így használod, akkor frissíti az NF (mezők száma), az NR (az eddig beolvasott rekordok száma), az FNR (az ebből a file-ból beolvasott rekordok száma) és a $0 változó értékét.
Megjegyzés: A getline az új $0 értéket használja bármilyen további szabályban megadott mintaillesztésre. A $0 azon értéke, ami az aktuális szabályt aktiválta elveszett (s.s.). Ezzel ellentétben a next kifejezés beolvassa a következő rekordot és a feldolgozást az első szabálytól kezdi.
A `getline var' kifejezés beolvassa a következő rekordot és a var változóban tárolja. Semmilyen más feldolgozás nem történik.
Például tegyük fel, hogy a következő sor a bemeneten egy megjegyzés vagy egy speciális szöveg és be akarod olvasni és tárolni, de egy szabályt sem akarsz aktiválni. A getline ezen formája ezt teszi lehetővé, ráadásul az awk fő rekord-beolvasás-és-minden-szabály-ellenőrzése ciklus az így beolvasott rekordot soha nem látja.
Az alábbi példa a bemenet minden második sorát felcseréli az előzővel, tehát ha a bemenet:
akkor az eredmény:
A program:
Ha a getline függvényt így használod, akkor csak az NR és az FNR (és természetesen a var) változók értéke változik meg. A beolvasott rekordot nem darabolja fel mezőkre, így sem a a mezők sem a $0 és az NF értéke nem változik meg.
A `getline < file' kifejezés beolvassa a következő rekordot a file-ból. Itt a file egy szöveg értékű kifejezés kell legyen, ami a file nevét adja meg. A `< file' kifejezést átirányításnak is szokták nevezni, mivel azt adja meg, hogy a bemenet valahonnan máshonnan (más irányból) jöjjön.
Például az alábbi program egy új rekordot olvas be a `secondary.input' file-ból, ha az első mező értéke tíz az aktuális rekordban.
Mivel nem a fő bemenetet használja, az NR és az FNR változók értéke nem változik meg, de az újonnan beolvasott rekordot feldarabolja mezőkre a normális módon, így a $0 és az NF is megváltozik.
A POSIX szabvány szerint a `getline < expression' nem egyértelmű, ha a kifejezés tartalmaz a `$'-on kívül egyéb operátort; például a `getline < dir "/" file' nem egyértelmű, mert az összefűzés operátor nincs zárójelek között. Ha több awk implementációval is használni szeretnéd a programodat, akkor a `getline < (dir "/" file)' kifejezést érdemes használni.
A `getline var < file' kifejezés beolvassa a következő rekordot a file-ból és a var változóban tárolja. Mint előbb, a file itt is egy szöveg értékű kifejezés kell legyen, ami a file nevét adja meg.
Ebben az esetben egyetlen beépített változó tartalma sem változik meg és a rekord nem lesz feldarabolva mezőkre. Egyedül a var változó kap új értéket.
Például az alábbi program a bemeneti file minden sorát kinyomtatja, kivéve azokat a rekordokat amelyek a `@include filename' kifejezést tartalmazzák. Ezeket a rekordokat a filename file tartalmával helyettesíti.
Érdemes megfigyelni, hogy az extra file neve nincs beépítve a programba, hanem a második mezőből olvassuk ki.
A close függvény biztosítja, hogy ha két azonos `@include' sor van a file-ban, akkor a megadott file tartalma kétszer lesz kinyomtatva.
A program egyik hibája, hogy beágyazott `@include' kifejezéseket nem tud feldolgozni (egy `@include' egy másik `@include' kifejezéssel megadott file-ban), mint ahogy egy igazi macro feldolgozó programnak kellene.
Egy másik program kimenetét átirányíthatod a getline-ba, a `command | getline' kifejezéssel. Ebben az esetben a command mint egy shell parancs fog lefutni, és a kimenetét az awk fogja használni, mint bemenetet. A getline egyszerre csak egy rekordot olvas be a csőből.
Például az alábbi program a bemenetét a kimenetre másolja, kivéve azokat a sorokat amelyek egy `@execute' szöveget tartalmaznak, amikor is a rekord többi részét mint shell parancs hajtja végre és az eredményt nyomtatja ki.
A close függvény biztosítja, hogy ha két azonos `@include' sor van a file-ban, akkor a megadott file tartalma kétszer lesz kinyomtatva.
Tehát, ha ez a bemenet:
a program ezt az eredményt produkálja:
A program végrehajtotta a who parancsot és annak eredményét nyomtatja ki. (Ha kipróbálod a programot, valószínű, hogy más eredményt fogsz kapni, mivel azokat a felhasználókat fogja kinyomtatni akik be vannak jelentkezve a rendszeren.)
A getline ilyen használata esetén a bemenetet feldarabolja, beállítja az NF értékét és a $0-t újraértékeli. Az NR és az FNR értéke nem változik meg.
A POSIX szabvány szerint a `expression | getline' nem egyértelmű, ha a kifejezés tartalmaz a `$'-on kívúl egyéb operátort; például a `"echo " "date" | getline' nem egyértelmű, mert az összefűzés operátor nincs zárójelek között. Ha több awk implementációval is használni szeretnéd a programodat, akkor a `("echo " "date") | getline' kifejezést érdemes használni. (A gawk helyesen kezeli ezt az esetet, de nem érdemes ebben bízni. A zárójelekkel egyébként is jobban olvasható, hogy mi is történik.)
Ha a `command | getline var' kifejezést használod, akkor a command kimenete a getline-ba lesz átirányítva majd a var változót is beállítja. Például az alábbi program beolvassa a mai dátumot és a pontos időt a current_time változóba a date segédprogram segítségével, majd kinyomtatja:
Ebben az esetben egyetlen beépített változó sem változik meg és a rekordot sem darabolja fel mezőkre.
A getline használata esetén bár a $0 és az NF értéke lehet hogy megváltozik, de a beolvasott rekordot nem teszteli minden mintával az awk programban, ami normális esetben történne. Ugyanakkor a beolvasást követő szabályokban az új rekordot használja.
Sok awk implementáció egyre korlátozza az egyszerre megnyitható csövek számát. A gawk-ban nincs ilyen korlátozás, annyi csövet nyithatsz meg, amennyit az operációs rendszer megenged.
A getline-nak egy érdekes mellékhatása van, ha a BEGIN szabályon belül használjuk. Mivel az átirányítás nélküli getline a parancssorban megadott file-okból olvas, az első getline kifejezés beállítja a FILENAME változót is. Általában a FILENAME-nek nincs értéke a BEGIN szabályon belül, mivel a file-ok feldolgozása még nem kezdődött meg.
Az alábbi táblázat összefoglalja a getline hat lehetséges használati módját, illetve megadja, hogy mely változók értéke változik meg.