Awk nie je ťažkopádny!

Celých 20 rokov som štruktúrované súbory parsovala cutom, grepom a sedom, 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äť sedu, 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 catu”?

cat vladcovia.txt | awk /Jozef/ 

A kultúrne, bez zbytočnej mačky:

awk /Jozef/ vladcovia.txt

Kultistom sedu 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 cutom 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 awku 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 sede 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 awku 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:

  1. Frantisek II. Rakoci
  2. ␣1705 (všimnime si medzeru!)
  3. 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 BEGINe 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 catom:

awk 1 vladcovia.txt

Áno, simulácia catu: 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 sube, resp gsube. 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.

2 thoughts on “Awk nie je ťažkopádny!

  1. 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???

Napísať odpoveď pre Róbert Novotný Zrušiť odpoveď

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *