Celých 20 rokov som štruktúrované súbory parsovala cut
om, grep
om a sed
om, ale odkedy som objavila awk
, moje skripty sú oveľa prehľadnejšie!
awk
je skvelá vec, a neverte nikomu, že je to skratka od awkward („ťažkopádny“). Umožňuje spracovávať, filtrovať, grepovať, a rezať textové súbory, ale syntax nie je psychedelická mäť sed
u, ale pravoverné, kultúrne a civilizované for
/if
/function
procedurálne programovanie!
A ako hovorí klasik Robinson Gnusoe, na pustý ostrov by som si zobral len awk
.
Kde robiť? Čo robiť?
awk
chrústa riadky zo vstupu a spracováva ich dľa uváženia. V bežnej situácii potrebujete (okrem dát) povedať:
- ktoré riadky spracovať? [predpis]
- čo s nimi urobiť? [akcia]
Bude kopa príkladov.
Ešte predtým si ale urobme historické okienko. Založme si cvičný súbor vladcovia.txt
s nasledovným obsahom:
Frantisek II. Rakoci, 1705-1711
Karol III., 1711-1740
Maria Terezia, 1740-1780
Jozef II., 1780-1790
Leopold II., 1790-1792
Frantisek I., 1792-1835
Ferdinand V., 1835-1848
Frantisek Jozef I., 1848-1916
Karol IV., 1916-1918
awk
ako grep
Najjednoduchšie použitie: filtračka. Ktoré riadky spracovať? Tie, ktoré spĺňajú predpis (pattern) tvorený regulárnym výrazom. Čo s nimi urobiť? Ciskac, čiže vypľuť na štandardný výstup.
Všetky záznamy o vládcoch Jozeficg zo zoznamu vládcov plus kandidát na ocenenie “nezmyselné použitie cat
u”?
cat vladcovia.txt | awk /Jozef/
A kultúrne, bez zbytočnej mačky:
awk /Jozef/ vladcovia.txt
Kultistom sed
u plesá srdce, predpisom je regulárny výraz uzatvorený do dvoch lomiek. Hľadajú sa všetci Jozefovia, teda nájde si i Jozef II., i František Jozef II..
Chceme len Pravých Jozefov(tm)? ^Jozef
hľadá Jozefov len na začiatku riadku, čiže František Jozef
nie je dôležitý.
Shell môže robiť s predpismi čiernu mágiu, preto sa jeho príkazy uzatvárajú do apostrofov:
awk '/^Jozef/' vladcovia.txt
Hneď to vidieť pri negácii — čo je, prirodzene, výkričník. Všetci používatelia, čo nie sú mnou?
awk '! /^Jozef/' vladcovia.txt
Vo všetkých príkladoch sme nikde nepísali, čo spraviť s riadkom (aká je akcia), ale awk
si domyslel, že ich chceme vypísať.
awk
ako cut
Praví vývojári obdivujú awk
nie preto, že sa vie prezliekať za grep
, ale preto, že dokáže dokonale pracovať s riadkami, ktoré obsahujú viacero položiek. Hej, veď máme cut
, kričí zlý jazyk, ale … cvakanie cut
om je polahoda do momentu, kým nezistíte, že polia sú oddelené niečim iným než jedným znakom, alebo ich nebodaj chcete vypísať v inom poradí. Rezanie /etc/passwd
je OK, ale pri vládcoch už cut
nefunguje, lebo položky sú oddelené rozličnými oddeľovačmi.
Ale awk
sa nedá zahanbiť. Implicitne vie chrústať položky oddelené bielym miestom.
Akcie a predpisy
Zistime, kedy vládla Mária Terézia:
- ktoré riadky spracovať? Len tie, ktoré sa začínajú na
Maria Terezia
. - čo s nimi urobiť? Vypísať len tretiu položku. (Prečo tretiu? Odpočítajme si položky oddelené bielym miestom na prstoch.)
Pred chvíľou sme videli len predpisy so štandardnou akciou (tlač riadok), ale tu už potrebujeme kombo predpisu a akcie. Filozofia jest: ak riadok spĺňa predpis, vykoná sa akcia.
Akcie zapisujú v dôverne známom kučeravozátvorkovom prostredí — cíťte vôňu C — a výpis rieši funkcia print
.
Program v awk
u teda bude vyzerať:
/^Maria Terezia/ { print $3 }
ČoTF je $3
? Tretia položka po posekaní. Čítajte ďalej!
Sekanie do položiek
awk
vezme vstupný riadok a automaticky ho poseká na jednotlivé položky na základe oddeľovača, ktorým je štandardne biele miesto.
V akcii potom môžeme používať špeciálne premenné z názvami $1
, $2
, atď., ktoré v sebe budú niesť hodnoty prvek, druhej, atď-tej položky.
So logically, $3
je tretie políčko.
Dohromady získame rozsah rokov:
awk '/^Maria Terezia/ { print $3 }'
Jú, a bonusovka: $0
obsahuje celý riadok.
Nemám predpis, nemám akciu…
Ak vynecháme predpis, akcia sa aplikuje na všetky riadky — takto možno vypísať zo súboru len prvé mená vládcov:
{ print $1 }
Výsledkom bude:
Frantisek
Karol
Maria
Jozef
Leopold
Frantisek
Ferdinand
Frantisek
Karol
Ak vynecháme akciu, automaticky sa vytlačí celý riadok. Videli sme vyššie pri filtri.
Ak vynecháme aj akciu, aj predpis, je to somarina, lebo nie je čo riešiť. awk
ácky skript musi mať aspoň jednu akciu.
awk
ako cat
Stačí toto:
awk '{ print $0 }' vladcovia.txt
Ak vynechame argumenty pre print
, i tak sa vytlačí celý riadok.
awk '{ print }' vladcovia.txt
Iné oddeľovače
Máte iný oddeľovač než štandardný “bielomiestový?” Napr. čiarku medzi menom a rokmi vlády? Nie je problém, stačí uviest parameter -F
. Vypísať celé mená vládcov, vrátane poradia, možno cez:
awk -F ',' '{ print $1 }' vladcovia.txt
Oddeľovač patrí do apostrofov/úvodzoviek, čo je záležitosť shellu, inak riskujeme podivnosti.
Pre každý riadok sa zobrala prvá položka a vypľula sa na výstup.
Výmena stĺpcov a lepenie reťazcov
Ak chcete vymeniť poradie stĺpcov, cut
chcípne úplne a v sed
e by ste konštruovali divý regulárny výraz s nahrádzaním grúp. Tuto je to jednoduché.
Chcete vypísať roky vládnutia a za nimi mená?
awk -F ',' '{ print $2 $1 }' vladcovia.txt
Toto bude fungovať, až na to, že roky budú začínať medzerou a budú nacapené na mená:
␣1705-1711Frantisek II. Rakoci
␣1711-1740Karol III.
␣1740-1780Maria Terezia
␣1780-1790Jozef II.
␣1790-1792Leopold II.
␣1792-1835Frantisek I.
␣1835-1848Ferdinand V.
␣1848-1916Frantisek Jozef I.
␣1916-1918Karol IV.
V awk
u nepotrebuje konkatenácia (lepenie) reťazcov žiadny operátor, teda žiadne plusky, či bodky. Reťazce napísané vedľa seba sú rovno zlepené. Hlúpe oddeľovanie:
{ print $2 " " $1 }
Medzi dve položky narveme jeden jednomedzerový reťazec (tie sú klasicky, v dvoch úvodzovkách).
Klasickejšie možno použiť čiarku, tá strčí medzi položky implicitný oddeľovač výstupu (output field separator), ktorým je štandardná … tadá, medzera. (Dá sa však nastaviť na iný, počkajte):
{ print $2, $1 }
Viacero oddeľovačov
Oddeľovač -F
nemusí byť len jeden znak (pozdravujem ťa, cut!
). V skutočnosti je to regulárny výraz! Ak máme roztodivný súbor s viacerými delimitermi (ehm, ehm, vladcovia.txt
s čiarkou alebo pomlčkou), stačí použiť
regulárny výraz [,-]
— teda sada znakov reprezentujúca buď čiarku alebo pomlčku.
Ak chceme vypísať najprv rok začatia vlády, potom rok skončenia vlády a potom celé meno, dajme to dohromady:
awk -F '[,-]' '{ print $2, $3, $1 }' vladcovia.txt
Vo výpise je jasne vidno jeden zádrheľ. Teda… nevidno, pretože zádrheľ spočíva v bielych miestach. Keď awk
rozsekáva riadok podľa oddeľovačov, robí to poriadne. Prvý riadok rozsekne takto:
Frantisek II. Rakoci
␣1705
(všimnime si medzeru!)1711
Het s medzerami!
Zvoľme lepší oddeľovač: napr. regulárny výraz, ktorý hovorí, že “buď oddeľujeme čiarkou, za ktorou ide medzera alebo pomlčkou.” Čiže , |-
, čo v príkaze urobí:
awk -F ', |-' '{ print $2, $3, $1 }' vladcovia.txt
Viacero predpisov
Pokojne môžeme mať aj viacero predpisov! Stačí ich nasekať za sebou, vrátane kučeravých zátvoriek. Zduplikovať každý riadok môžeme cez:
awk '{ print } { print }' vladcovia.txt
Riadok prejde všetkými predpismi a ak ich spĺňa, použije sa akcia. Keďže máme za sebou dva predpisy, ktoré sa týkajú každého riadka, máme dvojitý print
.
A áno, to isté by sa dalo cez:
awk '{ print; print }' vladcovia.txt
Sú situácie, keď naozaj chcete aplikovať viacero akcií naraz.
Špeciálne predpisy
Existujú dva špeciálne predpisy: BEGIN
, ktorý sa aplikuje pred začatím čítania vstupu a END
, ktorý sa vykoná po dokončení žutia vstupu.
Nakreslime nádherné čiary pred a za riadkami:
BEGIN ( print "---------------" } { print } END ( print "---------------" }
Vyzerá to divne, ale vieme to zapísať krajšie, len sa musíme doučiť premenné.
Premenné
Premenné sme už videli: minimálne špeciálne dolárovočíselné, ktoré sú zabudované. Vieme však tvoriť aj vlastné, tie sú klasické céčkoidné, bez dolárov.
BEGIN { CIARA="---------------"; print CIARA } { print } END { print CIARA }
Všimnime si dva riadky v akcii, oddeľujeme ich bodkočiarkou.
Toto je samozrejme príkaz na hrane čitateľnosti, ale potom si povieme, ako vytvárať awkové skripty v externých súboroch.
Awkové skripty
Ak skript začne vyzerať šialene, je čas ho vyhodiť do separátneho súboru. Napr. predošlý príklad, v separátnom súbore obal_ciarou.awk
:
BEGIN {
CIARA="---------------"
print CIARA
}
{
print
}
END {
print CIARA
}
Spustiť ho môžeme cez:
awk -f obal_ciarou.awk vladcovia.txt
Počítanie a premenné
Keďže ide o programovací jazyk, sú v ňom premenné, s ktorými možno rátať! Chcete počítač počet Františkov?
/Frantisek/ { F++ } END { print "Frantisek:", F }
Máme dva predpisy: v prvom budeme napočítavať do premennej F
, a v druhom, na konci, vypíšeme jej obsah.
Premenné nemajú typ, a netreba ich inicializovať, akurát niekedy nastávajú nepatrné podivnosti. Chcete hľadať Richardov?
/Richard/ { R++ } END { print "Richard:", R }
Žiadnych nemáme, a preto uvidíme:
Richard:
Premenná R
sa nikdy neinicializovala, a teda pri výpise sa považuje za prázdny reťazec. Ľa, špinavý trik majstrov:
print "Richard:", R + 0
Pripočítanie nuly k prázdnemu reťazcu je 0.
A ešte z iného súdka: chcete spočítať počet bashov v /etc/passwd
?
/\/bin\/bash/ { BASH++ } END { print "Bash:", BASH }
Bohužiaľ, musíme zaviesť “plot na dobytok”, teda escapovať lomky pomocou spätných lomiek.
Štelovanie výstupu
Vyššie sme hovorili, že niekedy chceme nastaviť iný oddeľovač výstupu. Na to slúži premenná OFS
, áno, output field separator. Môžeme ju nastaviť buď v BEGIN
e alebo rovno v rámci akcie, ukážka je tabulátor.
A OFS
má kamaráta, premennú definujúcu oddeľovač vstupu. Premenná FS
je presne to, čo nastavujeme parametrom -F
.
BEGIN {
FS=", |-"
OFS="\t"
}
{
print $2, $3, $1
}
Potom:
awk-f skript.awk vladcovia.txt
Výpis:
1705 1711 Frantisek II. Rakoci
1711 1740 Karol III.
1740 1780 Maria Terezia
1780 1790 Jozef II.
1790 1792 Leopold II.
1792 1835 Frantisek I.
1835 1848 Ferdinand V.
1848 1916 Frantisek Jozef I.
1916 1918 Karol IV.
Číslo aktuálneho záznamu: awk
ako nl
Existuje špeciálna premenná, NR
, ktorá udáva poradové číslo aktuálneho záznamu. Očíslovaní vládcovia?
awk '{ print NR, $0 }' vladcovia.txt
Číslo aktuálneho záznamu: awk
ako head
Ak poznáme NR
, môžeme (takmer) nestratiť hlavu. Predpisy nemusia byť len regulárnovýrazové, ale môžu byť tvorené ľubovoľnou podmienkou. Prvých desať riadkov? To je to isté ako ” NR
je menšie a rovné 10″:
awk 'NR <= 10 { print }' vladcovia.txt
Predpisom je v tomto prípade:
NR <= 10
Toto je trochu neefektívne, lebo podmienka sa overí pre každý riadok a teda sa prelezie celý súbor. Dá sa to aj krajšie, ale je to tiež trik.
Predpis je booleovská podmienka
Predpisom je ľubovoľná booleovská podmienka a akcia sa vykoná, ak je predpis pravdivý. V súlade s C zásadou je to hocičo, čo je nenulové, čo sa dá využiť pri finte s cat
om:
awk 1 vladcovia.txt
Áno, simulácia cat
u: predpis je vždy pravda, neuvedená akcia implicitné ciskanie.
Predošlá hlava sa dá prepísať aj takto:
1; NR >= 10 { exit }
Prvé pravidlo sa aplikuje na každý riadok. Druhé pravidlo: v momente, keď narazíme na riadok a jeho číslo je väčšie než 10, končíme spracovanie.
Podmienka pre položky
Podmienka sa môže týkať i konkrétnej položky. Napr. takto vypíšeme všetkých (jedného) vládcov, ktorí začali vládnuť v meruôsmom roku 1848:
awk -F ', |-' '$2 == "1848"' vladcovia.txt
Dokonca i tam môžeme používať regulárne výrazy, stačí použiť vlnku a lomky. Vládcovia, ktorí začali v 19. storočí? Aha:
awk -F ', |-' '$2 ~ /^18/' vladcovia.txt
Počet položiek v zázname
Opäť finta, kde cut
mrie: vypíšte len posledné položky zo záznamov! Nemusíme rátať na prstoch, máme špeciálnu premennú NF
(number of fields), ktorá vráti počet položiek.
Nezmyselná úloha ukáže, koľko položiek na ktorom riadku:
awk -F ', |-' '{ print $0, NF }' vladcovia.txt
Ale nie o tom som chcel. Premenná sa dá odolárovať! V tom prípade získame naozaj poslednú položku, tuto napr. roky skončenia vlády:
awk -F ', |-' '{ print $NF }' vladcovia.txt
Funkcie
Áno, máme zabudované funkcie. Je ich kopa: stačí pozrieť dokumentáciu. Naozaj si ju pozrite, sú to vaše kamarátky.
Nahradenie reťazca
Seďáci poznajú klasický príkaz s/../../
, awkáci fičia na sub
e, resp gsub
e. Prvé nahrádza prvý výskyt, druhý robí hromadné nahradenie na riadku.
Ukážka nahradí Rákoci
-ho maďarským prepisom:
awk '{ sub("Rakoci", "Rakoczi"); print }' vladcovia.txt
Funkcia berie tri parametre: “čo”, “čím” a “kde”. Ak “kde” vynecháme, nahrádza sa v celom riadku, teda funkcia priamo mení obsah premennej $0
! Nezabudnime preto zmenený riadok vypísať!
Ako ďalej
awk
je fakt veľký zver. Veď pán Brandejs o ňom napísal celý meganávod.
Oplatí sa tiež pozrieť Grymoire a tamojšiu sekciu o awk
.
Pomôcť môže aj seriál jednoriadkových skriptov.
potrebujem prehladat subor “test.anw”
a ked sa v nom vyskutuje retazec “rfc123456”, nech vypise na obrazovku ten riadok, aj s nasledujucimmi 5timi(alebo 6,7,8..10timi) riadkami… ako na to???
GNU
grep
podporuje parameter-A
(after), ktorý vyhľadá reťazec i príslušný počet riadkov za ním.Riadok a štyri riadky za ním možno dosiahnuť cez: