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
(see section Beépített változók)
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
(see section Explicit beolvasás getline
-al).
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, `='
(see section Értékadó kifejezések). 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
(see section A BEGIN
és az END
speciális minták)
szabályt kell használni. Például:
awk 'BEGIN { RS = "/" } ; { print $0 }' BBS-list
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:
$ awk 'BEGIN { RS = "/" } ; { print $0 }' BBS-list -| aardvark 555-5553 1200 -| 300 B -| alpo-net 555-3412 2400 -| 1200 -| 300 A -| barfly 555-7685 1200 -| 300 A -| bites 555-1675 2400 -| 1200 -| 300 A -| camelot 555-0542 300 C -| core 555-2912 1200 -| 300 C -| fooey 555-1234 2400 -| 1200 -| 300 B -| foot 555-6699 1200 -| 300 B -| macfoo 555-6480 1200 -| 300 A -| sdace 555-3430 2400 -| 1200 -| 300 A -| sabafoo 555-2127 1200 -| 300 C -|
Érdemes megfigyelni, hogy a `camelot' kezdetű sor nem lett feldarabolva. Az eredeti file-ban (see section Adat file-ok a példákhoz) a sor így néz ki:
camelot 555-0542 300 C
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 (see section Other Command Line Arguments).
awk '{ print $0 }' RS="/" BBS-list
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.
$ echo | awk 'BEGIN { RS = "a" } ; { print NF }' -| 1
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.
See section Több sorból álló rekordok, további részletekért.
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 (see section Reguláris kifejezések).
Á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
(see section Reguláris kifejezések).
$ echo record 1 AAAA record 2 BBBB record 3 | > gawk 'BEGIN { RS = "\n|( *[[:upper:]]+ *)" } > { print "Record =", $0, "and RT =", RT }' -| Record = record 1 and RT = AAAA -| Record = record 2 and RT = BBBB -| Record = record 3 and RT = -|
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.
See section A Simple Stream Editor, ahol további példák
találhatók az RS
és RT
használatára.
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 (see section Command Line Options).
"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(6) 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:
This seems like a pretty nice example.
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:
$ awk '$1 ~ /foo/ { print $0 }' BBS-list -| fooey 555-1234 2400/1200/300 B -| foot 555-6699 1200/300 B -| macfoo 555-6480 1200/300 A -| sabafoo 555-2127 1200/300 C
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
(see section Hogyan használjuk a reguláris kifejezéseket);
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.
$ awk '/foo/ { print $1, $NF }' BBS-list -| fooey B -| foot B -| macfoo A -| sabafoo C
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:
awk '{ print $NR }'
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:
awk '{ print $(2*2) }' BBS-list
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. (Az awk
nyelv operátorainak a precedencia listája,
csökkenô sorrendben a
section Operátorok precedenciája (Hogyan ágyazhatók operátorok egymásba)
alatt található meg.)
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, section Mezôk elérése, az
NF
beépített változó
(see section Beépített változók)
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:
$ awk '{ $3 = $2 - 10; print $2, $3 }' inventory-shipped -| 13 3 -| 15 5 -| 15 5 ...
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'.
(See section Matematikai operátorok.) 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.
See section Szövegek és számok konverziója.
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.
$ awk '{ $2 = $2 - 10; print $0 }' inventory-shipped -| Jan 3 25 15 115 -| Feb 5 32 24 226 -| Mar 5 24 34 228 ...
Olyan mezôkhöz is rendelhetô tartalom, amelyek nem részei a rekordnak. Például:
$ awk '{ $6 = ($5 + $4 + $3 + $2) > print $6 }' inventory-shipped -| 168 -| 297 -| 301 ...
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; see section Mezôk elérése), ugyanakkor az
NF
és az eddig nem tárgyalt kimeneti mezôelválasztó,
OFS
(see section Kimeneti elválasztó) 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:
if ($(NF+1) != "") print "can't happen" else print "everything is normal"
program részlet az `everything is normal' szöveget fogja kinyomtatni,
mivel a NF+1
-ik mezô természetesen nem szerepel a rekordban.
(Az awk
if-else
kifejezésrôl további információ a
see section Az if
-else
kifejezés alatt található.
A `!=' operátorról pedig a
section Változó típusok és az összehasonlító kifejezések
ad további információt.)
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:
$ echo a b c d | awk '{ OFS = ":"; $2 = "" > print $0; print NF }' -| a::c:d -| 4
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.
$ echo a b c d | awk '{ OFS = ":"; $2 = ""; $6 = "new" > print $0; print NF }' -| a::c:d::new -| 6
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:
$ echo a b c d e f | ../gawk '{ print "NF =", NF; > NF = 3; print $0 }' -| NF = 6 -| a b c
Ez a fejezet egy kicsit hosszabb lesz, mivel az awk
egyik alapvetô
működési elvét magyarázza el.
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:
moo goo gai pan
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 (see section Értékadó kifejezések).
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
(see section A BEGIN
és az END
speciális minták)
használatával teheted ezt meg. Az alábbi példában az FS
értéke a
","
lesz:
awk 'BEGIN { FS = "," } ; { print $2 }'
és ha a bemeneti sor:
John Q. Smith, 29 Oak St., Walamazoo, MI 42139
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:
John Q. Smith, LXIX, 29 Oak St., Walamazoo, MI 42139
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:
FS = ", \t"
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 (see section Escape szekvenciák), 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
(see section Reguláris kifejezések).
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:
$ echo ' a b c d ' | awk '{ print $2 }' -| b
de ez egy `a'-t nyomtat ki (extra szóközök vannak a betűk körül):
$ echo ' a b c d ' | awk 'BEGIN { FS = "[ \t]+" } > { print $2 }' -| a
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:
$ echo ' a b c d' | awk '{ print; $2 = $2; print }' -| a b c d -| a b c d
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:
$ echo a b | gawk 'BEGIN { FS = "" } > { > for (i = 1; i <= NF; i = i + 1) > print "Field", i, "is", $i > }' -| Field 1 is a -| Field 2 is -| Field 3 is b
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 (see section Command Line Options), ha az FS
értéke
""
a gawk
is így viselkedik.
FS
beállítása parancssorból
Az FS
értéke a parancssorból is beállítható, a `-F' opció
használatával:
awk -F, 'program' input-files
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:
# ugyanaz mint FS = "\\" awk -F\\\\ '...' files ...
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
(see section Escape szekvenciák), ami végül a
`\' jelet adja meg mint mezôelválasztó.
Egy speciális eset, ha "compatibility" módban
(see section Command Line Options) 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 (see section Command Line Options).
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:
/300/ { print $1 }
Á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:
$ awk -F- -f baud.awk BBS-list -| aardvark 555 -| alpo -| barfly 555 ...
A második sor nem egészen tökéletes. Az eredeti file-ban (see section Adat file-ok a példákhoz) így nézett ki:
alpo-net 555-3412 2400/1200/300 A
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:
arnold:xyzzy:2076:10:Arnold Robbins:/home/arnold:/bin/sh
Az alábbi program végignézi a jelszó file-t és kilistázza azokat a felhasználókat akiknél nincs jelszó megadva:
awk -F: '$2 == ""' /etc/passwd
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
(7) parancs a `/etc/passwd' file-nak csak az elsô sorát
nyomtatja ki.)
sed 1q /etc/passwd | awk '{ FS = ":" ; print $1 }'
Egy rossz awk
implementáció esetén a program a
root
sort nyomtatja ki, míg a gawk
valami ehhez hasonlót fog kiírni:
root:nSijPlPhZZwgE:0:0:Root:/:
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ô.)
FS == " "
FS == egy bármilyen karakter
FS == regexp
FS == ""
(Ezt a fejezetet kezdô felhasználók nyugodtan átugorhatják elsô olvasásnál, mivel az itt leírt programozási lehetôség csak kísérleti, és bonyolult lehet megérteni.)
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
(see section Szövegmanipuláló beépített függvények),
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.
10:06pm up 21 days, 14:04, 23 users User tty login idle JCPU PCPU what hzuo ttyV0 8:58pm 9 5 vi p24.tex hzang ttyV3 6:37pm 50 -csh eklye ttyV5 9:53pm 7 1 em thes.tex dportein ttyV6 8:17pm 1:47 -csh gierd ttyD3 10:00pm 1 elm dave ttyD4 9:47pm 4 4 w brent ttyp0 26Jun91 4:46 26:46 4:41 bash dave ttyq4 26Jun9115days 46 46 wnewmail
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.)
BEGIN { FIELDWIDTHS = "9 6 10 6 7 7 35" } NR > 2 { idle = $4 sub(/^ */, "", idle) # strip leading spaces if (idle == "") idle = 0 if (idle ~ /:/) { split(idle, t, ":") idle = t[1] * 60 + t[2] } if (idle ~ /days/) idle *= 24 * 60 * 60 print $1, $2, idle }
Az eredmény:
hzuo ttyV0 0 hzang ttyV3 50 eklye ttyV5 0 dportein ttyV6 107 gierd ttyD3 1 dave ttyD4 0 brent ttyp0 286 dave ttyq4 1296000
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
(see section Mennyi szöveg illeszkedik?), í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
(see section Szövegmanipuláló beépített függvények).
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:
Jane Doe 123 Main Street Anywhere, SE 12345-6789 John Smith 456 Tree-lined Avenue Smallville, MW 98765-4321 ...
Egy egyszerű program a file feldolgozására:
# addrs.awk --- simple mailing list program # Records are separated by blank lines. # Each line is one field. BEGIN { RS = "" ; FS = "\n" } { print "Name is:", $1 print "Address is:", $2 print "City and State are:", $3 print "" }
A programot futtatva ezt az eredményt kapjuk:
$ awk -f addrs.awk addresses -| Name is: Jane Doe -| Address is: 123 Main Street -| City and State are: Anywhere, SE 12345-6789 -| -| Name is: John Smith -| Address is: 456 Tree-lined Avenue -| City and State are: Smallville, MW 98765-4321 -| ...
Egy másik programot is bemutatunk a címlisták feldolgozására egy késôbbi fejezetben, see section Printing Mailing Labels.
Az alábbi táblázat összefoglalja, hogy a rekordok hogyan lesznek
feldarabolva az RS
értékétôl függôen (a `==' egyenlôséget
jelent):
RS == "\n"
RS == egy bármilyen karakter
RS == ""
FS
-tôl függetlenül.
Kezdô és záró üres sorokat a file-ban nem veszi figyelembe.
RS == regexp
A gawk
mindig beállítja az RT
változót az RS
-re
illeszkedô szövegre.
getline
-al
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.
getline
bemutatása
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.
getline
használata argumentum nélkül
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:
awk '{ if ((t = index($0, "/*")) != 0) { # value will be "" if t is 1 tmp = substr($0, 1, t - 1) u = index(substr($0, t + 2), "*/") while (u == 0) { if (getline <= 0) { m = "unexpected EOF or error" m = (m ": " ERRNO) print m > "/dev/stderr" exit } t = -1 u = index($0, "*/") } # substr expression will be "" if */ # occurred at end of line $0 = tmp substr($0, t + u + 3) } print $0 }'
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; see section Mezôk elérése), az NR
(az eddig beolvasott rekordok száma;
see section Hogyan történik a feldarabolás rekordokra), 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.
See section A next
kifejezés.
getline
-alA `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:
wan tew free phore
akkor az eredmény:
tew wan phore free
A program:
awk '{ if ((getline tmp) > 0) { print tmp print $0 } else print $0 }'
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.
getline
-alA `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.
awk '{ if ($1 == 10) { getline < "secondary.input" print } else print }'
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.
getline
-alA `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.
awk '{ if (NF == 2 && $1 == "@include") { while ((getline line < $2) > 0) print line close($2) } else print }'
É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.
See section Bemeneti és kimeneti file-ok és csövek lezárása..
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. See section An Easy Way to Use Library Functions, amely tartalmaz egy megoldást beágyazott `@include' kifejezésekre.
getline
-al
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.
awk '{ if ($1 == "@execute") { tmp = substr($0, 10) while ((tmp | getline) > 0) print close(tmp) } else print }'
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.
See section Bemeneti és kimeneti file-ok és csövek lezárása..
Tehát, ha ez a bemenet:
foo bar baz @execute who bletch
a program ezt az eredményt produkálja:
foo bar baz arnold ttyv0 Jul 13 14:22 miriam ttyp0 Jul 13 14:23 (murphy:0) bill ttyp1 Jul 13 14:23 (murphy:0) bletch
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.)
getline
-al
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:
awk 'BEGIN { "date" | getline current_time close("date") print "Report printed on " current_time }'
Ebben az esetben egyetlen beépített változó sem változik meg és a rekordot sem darabolja fel mezôkre.
getline
változatairól
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 (s.s.).
(See section A BEGIN
és az END
speciális minták,
és see section Információt hordozó beépített változók.)
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.
getline
$0
, NF
, FNR
és az NR
változókat.
getline var
FNR
és az NR
változókat.
getline < file
$0
és az NF
változókat.
getline var < file
command | getline
$0
és az NF
változókat.
command | getline var
Go to the first, previous, next, last section, table of contents.