Awk
je programovaci jazyk pro praci s textem. V textove orientovanem UNIXu jej pouzivame take pro automatickou konstrukci prikazu, prikazovych souboru a predzpracovani vstupnich dat. Nazev Awk pochazi ze jmen jeho autoru: Alfred V. Aho, Peter J. Weinberger a Brian W. Kernighan. Interpretem tohoto jazyka je prikaz (program) awk
. Jeho verzi se vsak vyskytuje vice. Nejvice funkci ma zrejme implementace awk
z projektu GNU (gawk
) a ji je nasledujici text venovan.
$ awk program [soubory]
Program muzeme take cist ze souboru, potom zadame:
$ awk -f soubor [soubory]
Program awk
cte radky bud ze zadanych souboru nebo ze standardniho vstupu. Vystup smeruje na standardni vystup.
Kdyz napisete awk program a chcete, aby se startoval jako normalni program na zacatek date:
#! awk -f
To zaridi, ze se vas program tvari jako normalni seriozni programek.
vzorek { akce }
vzorek { akce }
atd.
V kazdem radku ctenem ze vstupu se hleda urceny vzorek. Pokud se najde, provede se s radkem zadana akce. Pote, co se pouziji vsechny vzorky, precte se ze vstupu dalsi radek a operace se opakuji.
Jak vzorek, tak akce se smi vynechat. Nelze vsak vynechat oboji soucasne. Pokud neni ke vzorku urcena akce, potom se vyhovujici radek zkopiruje na vystup. Pro radek vyhovujici vice vzorkum bude akce provedena vicekrat. Radek nevyhovujici zadnemu ze zadanych vzorku se ignoruje.
Pokud vynechame vzorek, potom se akce provede pro kazdy nacteny radek. Popis akce se musi uzavrit do slozenych zavorek '{ }
'. Tim se popis akce rozpozna od popisu vzorku.
awk
cte, delime do zaznamu (records) ukoncenych oddelovacem zaznamu. Implicitnim oddelovacem zaznamu je znak noveho radku. V tomto pripade je zaznamem jeden radek. Cislo aktualniho zaznamu awk
udrzuje v promenne NR
.
Kazdy zpracovavany zaznam se deli do polozek (field). Polozky se implicitne oddeluji bilym mistem (mezera, tabulator), lze vsak explicitne nastavit jinou hodnotu oddelovace polozek. Na jednotlive polozky se odkazujeme $1, $2
atd. Udaj za znakem dolar je cislo (ne jenom cislice). Identifikatorem $0
se odkazujeme na cely zaznam. Pocet polozek v aktualnim zaznamu je ulozen v promenne NF
.
Chceme-li zmenit implicitni nastaveni oddelovacu zaznamu a polozek, nastavime novou hodnotu do promenne: RS
pro oddelovac zaznamu a FS
pro oddelovac polozek. Obsah techto promennych muzeme zmenit obecne na regularni vyraz (v jinych verzich awk
pouze na libovolny jeden znak). Oddelovac polozek muzeme take nastavit na prikazovem radku pri spousteni awk
volbou -Fc
, kde c
je oddelovac polozek.
Je-li oddelovac zaznamu prazdny, potom se jako oddelovac chape prazdny radek na vstupu. Oddelovaci polozek potom jsou znaky mezera, tabulator a novy radek.
V promenne FILENAME
je ulozeno jmeno aktualniho vstupniho souboru ('-' v pripade standardniho vstupu).
... | awk '{ print }' | ...
Vzorek jsme vynechali, a proto se akce print
provede pro vsechny vstupujici radky. Akce print
bez parametru opise cely radek na vystup. Uzitecnejsi bude vybrat si urcite polozky a tyto vypsat, napr. prvni dve polozky v opacnem poradi:
Takovy zapis akce na prikazovem radku spoustejicim awk
musi byt nutne uzavren do dvojice apostrofu, aby nedoslo k expanzi dvojic znaku $1
a $2
na pozicni parametry shellu, ale aby se v nezmenene podobe predaly awk
.
Polozky oddelene v zapisu akce print
carkou se na vystupu oddeli aktualne nastavenou hodnotou oddelovace polozek. Polozky oddelene pouze mezerou se spoji bez oddelovace. Vyzkousejme si:
{ print $2 $1 }
Akce print
umi vypisovat i obsahy promennych a textove retezce, napr.:
{ print "Cislo zaznamu=" NR, "Pocet polozek=" NF, $0 }
Takto zadany program pred kompletnim zaznamem vypise cislo zaznamu a pocet polozek v aktualnim zaznamu.
Vystup muzeme rozdelit i do vice vystupnich souboru. Napr. program:
{ print $1 >"soubor1"; print $2 >"soubor2" }
zapise prvni polozku do souboru soubor1
a druhou polozku do souboru soubor2
. Lze pouzit i zapis >>
. Potom se do souboru pridava za konec. Jmeno souboru muze byt promenna, obsah zapisovany do souboru muze byt konstanta. Muzeme tedy napsat napr.:
{ print "nesmysl" >>$2 }
Jako jmeno souboru se pouzije obsah druhe polozky (pozor, nesmi byt prazdna). V tomto priklade bude pocet radku v jednotlivych souborech znamenat cetnost slov ve druhem poli.
Podobne lze vystup z akce print
predat rourou procesu. Napr. poslat postou na adresu 'zaznamenej':
{ print | "mail zaznamenej" }
Pokud si tento priklad vyzkousime, zjistime, ze se procesu preda cely vstup naraz, tj. nepredava se po jednotlivych zaznamech a navic se preda az po nacteni konce vstupu.
Pomoci promennych OFS
a ORS
muzeme samostatne nastavit oddelovace pouze pro vystup. Obsahem OFS
se oddeli vypisovane polozky a obsahem ORS
se oddeli vypisovane zaznamy.
Jazyk awk
take poskytuje moznost formatovanych vystupu pomoci akce printf
. Tato akce se zapisuje nasledujicim zpusobem:
printf format, vyraz, vyraz, ...
Popisovacem format
sdelime strukturu vystupu a prostrednictvim nadefinovane struktury vypiseme jednotlive vyrazy
. V popisovaci format
se pouziva stejna syntaxe jako v jazyce C. Uvedme si ve strucnosti moznosti:
Do retezce format
vkladame text, ktery se ma vypsat. Na mista, kde se ma vypsat vysledek vyrazu, vlozime popisovac ve tvaru:
%priznakysirkapresnosttypkonverze
Prvnimu popisovaci (kazdy popisovac zacina vzdy znakem %
) se priradi vysledek prvniho vyrazu, druhemu popisovaci vysledek druheho vyrazu atd. Jednotliva pole popisovace maji tento vyznam (vsechna pole vyjma konverze
jsou nepovinna):
priznaky
]se uvadeji zadny nebo vice. Mozne
priznaky jsou:
mezera
]Stejne jako priznak +, jenom se
misto kladneho znamenka vytiskne mezera. Pokud se uvede
+ i mezera, potom + vyhraje.
#
]Vystup se prevede do alternativni podoby.
Podrobnosti jsou uvedeny u popisu kazde konverze.
sirka
]Vyznam pole zalezi na typu pouzite konverze.
presnost
]Presnost uvadi, kolik cislic se ma
vypsat vpravo od desetinne tecky. Cislu odpovidajici
presnosti musi predchazet tecka. Pokud se uvede tecka bez
cisla, pouzije se hodnota 0. Presnost lze zadat pouze
s konverzemi e
, E
, f
, g
a G
.
typ
]Pole muze obsahovat znaky h
, l
nebo L
. Symbol h
sdeluje, ze se argument pred
vystupem prevede na format short
, typ l
prevede
na format long int
a typ L
na format long
double
.
konverze
]Konverze obsahuje jeden znak, ktery
sdeluje, jak se ma vyraz
vytisknout.
Na miste konverze
se smeji pouzit tyto symboly:
i
nebo d
] int
), ktery se
interpretuje jako cislo se znamenkem. Pole sirka
muze obsahovat minimalni pocet znaku, na ktery se cislo
vypise. Implicitne je sirka 1.
Vyznam priznaku #
neni definovan.
o
]Celociselny argument bez znamenka se vytiskne
osmickove.
Vyznam pole sirka
je stejny jako u konverze i
.
Priznak #
zvysi sirku tak, aby prvni cislice byla
nula.
u
]Celociselny argument bez znamenka se vytiskne
desitkove.
Vyznam pole sirka
je stejny jako u konverze i
.
Vyznam priznaku #
neni definovan.
x
]Celociselny argument bez znamenka se vytiskne
sestnactkove. Na miste cislic se pouziji i znaky abcdef.
Vyznam pole sirka
je stejny jako u konverze i
.
Priznak #
zvysi sirku tak, aby prvni cislice byla
nula.
X
]Celociselny argument bez znamenka se vytiskne
sestnactkove. Na miste cislic se pouziji i znaky ABCDEF.
Vyznam pole sirka
je stejny jako u konverze i
.
Priznak #
zvysi sirku tak, aby prvni cislice byla
nula.
f
]Argument ve dvojnasobne presnosti double
se prevede do tvaru '[-]ddd
.ddd
'. Pole sirka
uvadi minimalni pocet vytisknutych znaku. Pole presnost
muze za teckou specifikovat pocet desetinnych
mist. Po uvedeni priznaku #
se vypise desetinna tecka,
i kdyz nenasleduje zadna desetinna cislice.
e
]Argument ve dvojnasobne presnosti double
se prevede do tvaru '[-]d
.ddd
edd
'. Exponent budou vzdy alespon dve cislice.
Zobrazuje-li se hodnota 0, potom je i exponent nulovy.
Vyznam pole presnost
a priznaku #
je stejny
jako u konverze f
.
E
]Stejne jako e
s tim rozdilem, ze se
misto e vypise E.
g
]Stejne jako f
nebo e
. Konverze
e
se pouzije tehdy, pokud exponent je mensi nez -4
nebo je vetsi nez presnost.
G
]Stejne jako g
s tim rozdilem, ze se
misto e vypise E.
c
]Celociselny argument se prevede na typ unsigned char
a vysledny znak se vypise (tj. ordinalni
hodnotu prevede na ASCII znak).
Vyznam pole presnost
a priznaku #
je
nedefinovan.
s
]Predpoklada se, ze argumentem je retezec znaku
(char *
). Vypisi se znaky retezce az po (ale bez)
ukoncovaciho null
znaku. Pole sirka
predstavuje maximalni pocet znaku, ktere se vypisi.
Vyznam priznaku #
je nedefinovan.
Akce printf
negeneruje zadne vystupni oddelovace. Vsechny se
museji specifikovat ve formatu
. Uvedme si priklad pouziti:
{ printf "Prumer=%8.2f, pocet pokusu=%10ld\n", $1, $2 }
Prvni polozka se vytiskne jako cislo v pohyblive radove carce
celkem na 8 znaku se dvema cislicemi za desetinnou teckou.
Druha polozka se vytiskne jako long integer na 10 znaku. Znak
\n
predstavuje novy radek.
BEGIN
a END
jsou specialnimi pripady vzorku. Vzorek BEGIN
specifikuje akci, ktera se ma provest drive, nez se precte prvni zaznam vstupu. Naopak vzorek END
popisuje akci, ktera se provede po zpracovani posledniho cteneho zaznamu. Timto zpusobem muzeme ridit zpracovani pred a po cteni zaznamu.
Jako priklad uvedme nastaveni specifickeho oddelovace polozek a vytisknuti poctu nactenych zaznamu:
BEGIN { FS = ":" }
...zbytek programu...
END { print NR }
BEGIN
musi byt jako prvni vzorek (je-li uveden), END
musi byt poslednim vzorkem.
awk
je posloupnost prikazu vzajemne oddelenych novym radkem nebo strednikem.
awk
promenne zpracovava podle kontextu: bud jako numericke hodnoty (v pohyblive radove carce), nebo jako retezce znaku. Retezce se na numericke hodnoty prevadeji podle potreby. Potom napr.
x = 1
je typicky numericky prirazovaci prikaz, ale v prikazu
x = "3" + "4"
se retezce prevedou na numericke hodnoty a promenne x se priradi numericka hodnota 7. Retezce, ze kterych nelze ziskat numerickou hodnotu, maji hodnotu 0.
Promenna, ktere drive nebyla prirazena hodnota, ma hodnotu nula. Internim promennym awk
se prirazuji hodnoty automaticky (viz dale). Proto napr. program
{ s1 += $1; s2 += $2 }
END { print s1, s2 }
muze k promenne s1
pricitat. Promenna interpretujici se jako retezec bez prirazene hodnoty obsahuje prazdny retezec.
Potrebujeme-li se ujistit, ze promenna bude chapana jako numericka, pricteme k ni hodnotu 0. Potrebujeme-li naopak promennou interpretovat jako retezec, pripojme k ni prazdny retezec, napr.
b = 12 ""
I kdyz se numericke promenne zpracovavaji a ukladaji v pohyblive radove carce, desetinna tecka a cislice za ni se vypisuji jenom tehdy, pokud je desetinna cast nenulova. Cislo se na retezec konvertuje podle obsahu promenne CONVFMT
volanim sprintf
. Retezec se na cislo konvertuje volanim atof
.
awk
uvedme ilustracni priklad: Chceme awk
pouzit na prevod vystupu prikazu ls -l do klasickeho DOSovskeho tvaru vypisu adresare, tj. ze tvaru:
-rw-r--r-- 1 simekm staff 173741 May 16 21:10 text2.tex
do tvaru:
text2.tex 173741 May 16 21:10
Sestavime-li kolonu:
ls -l | grep -v ^total | awk -f dirp
potom pro awk
(v Systemu V - napr. Solaris, IRIX - pouzijte misto awk program nawk
, pokud nemate popisovane gawk
) potrebujeme nasledujici program v souboru dirp
:
BEGIN {print "User is " ENVIRON["LOGNAME"];
print }
{printf "%-18s %10i %3s %2i %4s\n", $9, $5, $6, $7, $8;
suma = suma + $5 }
END {printf " %4i file(s)%11i bytes\n", NR, suma }
Prikazem grep -v ^total
zrusime nevyznamny radek zacinajici slovem total. Pole ENVIRON
popiseme v nasledujici casti.
/L.*x/
vypise vsechny radky, ktere obsahuji nejprve znak L
a potom x
. Hledani vyhovujiciho vzorku muzeme omezit napr. na urcitou polozku. Napr. program
$1 ~ /^[Ll].*x$/
vypise ty radky, jejichz prvni polozka zacina pismenem L
nebo l
a konci pismenem x
. Operator !~
vybere ten radek, ktery vyhovujici vzorek neobsahuje.
<, >, ==, != , >= a <=
. Napr. program
$2 > $1 + 99
vypise ty radky, jejichz druha polozka je alespon o 100 vetsi nez polozka prvni. Aritmeticke operatory se pouzivaji stejne jako v aritmetickych expanzich shellu. Napr. program
NF % 2 == 0
vypise vsechny radky se sudym poctem polozek. Relacni operatory se mohou pouzivat jak pro aritmeticke srovnavani, tak i pro porovnavani retezcu. Proto napr. program
$1 >= "s"
vybere ty radky, jejichz prvni polozka zacina znakem s, t, u atd.
&&
(AND - logický součin), ||
(OR - logický součet) a !
(NOT - negace). Proto např. vzorek
$1 >= "l" && $1 < "o" && $1 !~ /^l.*x$/
způsobí výpis řádku, jehož první položka začíná písmenem l
až n
a zároveň položka nezačíná l
a zároveň nekončí x
. Operátory && a || se vyhodnocují zleva doprava. Vyhodnocování se zastaví v okamžiku, kdy je výsledek vzorku jasný.
/start/,/stop/
vypíše všechny řádky od řádku vyhovujícího vzorku /start/
po řádek vyhovující vzorku /stop/
. Např. program
NR == 100, NR == 200
vypíše záznamy (řádky) 100 až 200.
$1
, ...) požívají stejných vlastností jako proměnné. Mohou se používat jak v aritmetickém, tak i řetězcovém kontextu. Lze jim také přiřadit hodnotu. Proto lze napsat např.
{ $2 = NR; print }
nebo dokonce
{ $1 = $2 + $3; print $0 }
Odkazy na konkrétní položky se mohou také vyjádřit numerickým výrazem, např.
{ print $i, $(i+1), $(i+n) }
Jazyk awk
dovoluje použít příkazy pro řízení toku obvyklé u vyšších
programovacích jazyků. Jde o tyto příkazové konstrukce:
if (podmínka) příkaz [ else příkaz ]
while (podmínka) příkaz
do příkaz while (podmínka)
for (výraz1; výraz2; výraz3) příkaz
for (proměnná in pole) příkaz
break
continue
next
delete pole[index]
exit [ výraz ]
{ příkaz[; příkaz ... ] }
Z výše uvedených konstrukcí můžeme vytvořit např. tyto příklady
{ if ($3 > 1000)
$3 = "moc velké"
print
}
Následující příklad vytiskne vždy jednu položku na jeden řádek:
{ i = 1
while (i <= NF) {
print $i
++i
}}
V příkladu jsme si ukázali, že na místě příkazu smí být i více příkazů uzavřených do složených závorek. Následující příkaz provede totéž:
{ for (i = 1; i <= NF; i++) print $i }
Příkaz break
okamžitě ukončí provádění cyklu while
nebo for
. Příkaz continue
přejde ihned na novou iteraci cyklu.
Příkazem next
přejdeme na zpracování dalšího řádku (záznamu) vstupu. Příkaz exit
je totéž, co načtení konce vstupu (souboru).
Do programu pro awk
lze vkládat komentáře. Řádek s poznámkou musí začínat znakem #.
awk
předem nedeklarují. Jako příklad použití pole uveďme
{ x[NR] = $0 }
Tímto programem načteme celý vstup do jednorozměrného pole a zpracujeme jej až v akci náležející speciálnímu vzorku END
.
Prvky pole můžeme indexovat také nenumerickými hodnotami, např. řetězci. Ukažme si opět příklad použití
/modra/ { x["modra"]++ }
/cervena/ { x["cervena"]++ }
END { print x["modra"],
x["cervena"] }
Pole v awk
mohou být obecně n-rozměrná. Jejich prvky se totiž ukládají tak, jak je tomu u asociativních pamětí. S prvkem je uložen i jeho index. V případě více prvkových položek se jednotlivé indexy od sebe oddělují oddělovačem (tuto hodnotu lze změnit proměnnou SUBSEP, viz dále). Potom např.
i = "A"; j = "B"; k = "C"
x[i,j,k] = "hello, world\n"
do paměti uloží index ve tvaru A\034B\034C
. V příkazu for
můžeme použít zvláštní operátor in
následovně:
for (proměnná in pole) ...
Operátor in
v příkazu for
zajistí, že proměnné se budou postupně přiřazovat všechny prvky pole. Prvky jsou přiřazovány obecně v náhodném pořadí. Pokud obsah proměnné změníme nebo pokud současně zpřístupňujeme i jiné prvky pole, nastane zřejmě chaos. Je-li pole vícerozměrné, můžeme pro uložení všech indexů (jako řetězce) použít jednu proměnnou.
Operátor in
můžeme použít i v příkazech if
a while
ke zjištění, zda element určitého indexu se v poli nachází. Můžeme napsat
if (hodnota in pole) print pole[hodnota]
V případě vícerozměrných položek používáme zápis např.
if (("A","B","C") in x)
print x["A","B","C"]
Prvky pole rušíme příkazem delete
např. následovně:
delete x["A","B","C"]
awk
zahrnuty. Definují se tímto způsobem:
function jméno(seznam_parametrů) { příkazy }
Při volání funkce se formální parametry nahradí aktuálními. Pole se předávají odkazem, ostatní proměnné hodnotou.
Lokální proměnné se ve funkcích definují dost zvláštním způsobem. Je to zapříčiněno faktem, že původní awk
pojem lokální proměnné nezná. Lokální proměnné se definují v rámci seznamu_parametrů tak, že se uvedou na konci a oddělí se více mezerami, např.:
function f(p, q, a, b)
{ # proměnné a, b jsou lokální
... }
/abc/ { ... ; f(1, 2); ... }
Levá kulatá závorka musí následovat bezprostředně za jménem funkce (bez bílého místa). Tím se odstraní případné nejednoznačnosti syntaxe (možnost záměny s konkatenací).
Z funkce se smí volat jiná funkce a funkce smějí být rekurzivní. Na místě slova function
se smí použít jenom func
(rozšíření GNU verze).
Retezec format funkce strftime() obsahuje jak identifikaci casovych udaju (zacinaji znakem %), tak i normalni text - ten se opise beze zmen. Casove udaje jsou nasledujici (uzivatel by mel mit moznost konfigurovat udaje predavane touto funkci podle narodnich zvyklosti):
Uvedme si priklad:
strftime("Dnes je %d. %m. %Y a mame %H:%M hodin.")
Vyse uvedene escape posloupnosti se mohou pouzit i uvnitr jednoznakoveho RE reprezentujiciho tridu znaku. Napr. '/[ \t\f\n\r\v]/' vyhovuje vsem znakum typu "bile misto".
Vystup na standardni chybovy vystup muzeme poslat napr. timto prikazem:
print "Stala se chyba!" > "/dev/stderr"
.:/usr/lib/awk:/usr/local/lib/awk
P000:3:zk:Architektura pocitacu:brandejs
P004:2:zk:UNIX:brandejs
I011:2:zk:Semantiky programovacich jazyku:
zlatuska
Ukol zni: vypsat obsah databaze v "citelnem" tvaru a prevest prihlasovaci jmeno na prijmeni a prvni pismeno krestniho jmena. Pro tento ucel si vytvorme napr. soubor logins
s nasledujicim obsahem:
adelton:Jan:Pazdziora
brandejs:Michal:Brandejs
kas:Jan:Kasprzak
kron:David:Kostal
zlatuska:Jiri:Zlatuska
Pro prevod prihlasovaciho jmena na prijmeni pak pouzijeme jednorozmerne pole, kde indexem bude prihlasovaci jmeno a obsahem zkonstruovana dvojice prijmeni a krestniho jmena. Pole naplnujeme pouze jednou, a to v casti BEGIN
. Databaze predmetu necht se nacita ze standardniho vstupu.
... | awk -F':' 'BEGIN { while ( getline
<"logins" == 1 ) {
logins[$1]=sprintf("%s %1.1s.", $3, $2) } }
{ pocet=split($5,loginy,",")
for (i=1; i <= pocet; i++) {if (i == 1)
last = logins[loginy[i]]
else last = last ", " logins[loginy[i]]; }
printf("%-5s %-50.50s %1s %2s %s\n",$1,$4,
$2,$3,last) }'
Nasledujicim programem spocitame pocet vsech ruznych slov v textu:
BEGIN { FS="[^a-zA-Z]" }
{ for (i=1;i<=NF;i++) words[$i] = "" }
END { delete words[""]
# prazdne polozky nepocitame
for (i in words) sum++
print "V textu bylo " sum " ruznych
slov"
}
A dalsi vzorovy program vykonava totez co prikaz wc
:
{words+=NF; chars+=1+length($0)}
END {print NR,words,chars}
Tenhle program vymaze vsechny ceckove komentare ze souboru:
{
if(t=index($0,"/*")) {
if(t > 1) {
tmp=substr($0 , 1 , t - 1)
else
tmp=""
u=index(substr($0, t + 2),"*/")
while(u == 0)
getline
t=-1
u=index($0,"*/")
}
if(u <= length($0) - 2)
$0=tmp substr($0,t + u + 3)
else
$0=tmp
}
print $0
}