Awk

Allikas: Kuutõrvaja

Awk

Skriptkeel Awk on loodud tekstiliste andmetega manipuleerimiseks. Nimi Awk on akronüüm autorite perekonnanimedest - Aho, Weinberger, Kernighan. Segadust tekitav on paljude Awki versioonide levik, mis erinevad põhiliselt regulaaravaldiste tõlgendamisvõime poolest. Käesolev tegeleb GNU Awki versiooniga, mis muuhulgas tähendab seda, et saab kasutada laiendatud metasümbolite hulka (ingl. k. extended metacharacter set).

Programmeerimise mudel

Awk on sisendi poolt juhitav, mis tähendab, et tööosas näidatud käske rakendatakse vaikimisi Awki sisendisse suunatud andmete igale reale. Vaikimisi käsitleb Awk reana kahe järjestikulise '\n' -i vahele jäävat teksti. Awki skript võib koosneda kolmest osast:

ALGUSOSA - vastavad käsud täidetakse üks kord kõige alguses 

TÖÖOSA - vastavad käsud rakendatakse järjekorras igale sisendi reale 

LÕPUOSA - vastavad käsud täidetakse üks kord kõige lõpus

Neid osi märgitakse Awki skriptis selliselt, käsud kirjutatakse loogade vahele

BEGIN { algusosa käsud }
      { tööosa   käsud }
END   { lõpuosa  käsud }

Awkilik Awki kasutamine toimub rõhuasetusega tööosale. Järgnevas kirjeldame esituse selguse huvides keelevahendite süntaksit algusosa abil.

Awki käivitamine

Awki käske saab anda käsurealt või koondades nad ühte faili. Sellist käivitamisõigusega tekstifaili nimetatakse Awki skriptiks. Awki kasutamisel on võimalik tarvitada IO ümbersuunamist.

Toome näite, kuidas kahes tulbas ja tühikutega eraldatud sisendandmetel tulpade järjekord ära vahetada.

Olgu selline algtekst

bash~$ cat nimed
Miima   1959
Priit   1963
Mart    1940
Peeter  1988

Käsurealt toimub tulpade vahetamine selliselt

bash~$ cat nimed | awk '{ print $2, $1 }'
1959 Miima
1963 Priit
1940 Mart
1988 Peeter

Awki skripti, mis vahetab tulbad, on selline

#!/usr/bin/awk -f
{ print $2, $1 }

Skript sisaldab vaid tööosa ja käivitatakse näiteks nii

bash~$ cat nimed | vaheta.tulbad.awk

Samaväärne oleks jätta skriptist esimene interpretaatorit näitev rida ära ja käivitada Awk selliselt

bash~$ awk -f vaheta.tulbad.awk nimed

Heaks kombeks on lõpetada skripti nimi viitega interpretaatorile.

Kui juhtumisi on failis erinevad tulbad eraldatud tühiku asemel ":", või mõne muu märgiga siis saab seda määrata järgnevalt

cat nimed | awk -F':' '{ print $2, $1 }'

Ka arvutada saab awkiga, näiteks konvertida baite megabaitideks

echo '1452360394' | awk '{ foo = $1 / 1024 / 1024 ; print foo "MB" }'
1385.08MB

Muutuja ja avaldis

Awkis ei ole vaja muutujaid enne kasutamist deklareerida ning puuduvad muutuja tüübid. Sõltuvalt kontekstist käsitletakse muutujat kas stringi või arvuna. Muutuja nimi peab algama tähega ja võib sisaldada peale tähtede ka numbreid ja alakriipse.

Toome näite muutujate kasutamisest

#!/usr/bin/awk -f
BEGIN {
x = 15
y = 2
w = "Tartu"
z = "linn"
x_korda_y = x * y
print x " x " y " = " x_korda_y
tartu_linn = w z
print w " ja " z " on " tartu_linn
}
  • skript sisaldab vaid algusosa
  • defineeritakse neli muutujat: x, y, w, z
  • sooritatakse tehe arvudega ja stringidega ning trükitakse vastavad tulemused

Sisseehitatud käsud ja funktsioonid

Käsku print me oleme juba tarvitanud. Tema järgi kirjutatakse jutumärkidesse teksti ja ilma jutumärkideta muutujad, näites nimi ja x

print "Teie nimi on " nimi "ja te olete " x " aastat vana."

Tihti on otstarbekam kasutada käsku printf, mis sarnaselt C samanimelisele funktsioonile võimaldab väljundit paindlikumalt formateerida. Trükime ekraanile tehte 5/3 tulemuse neljakohalisena, millest kaks on peale koma.

#!/usr/bin/awk -f
BEGIN {
a=5/3
printf ("%4.2f\n", a);
}

Lisaks on olemas sellised sisseehitatud funktsioonid

  • cos(x) - tagastab radiaanides esitatud argumendi koosinuse
  • sin(x) - tagastab radiaanides esitatud argumendi siinuse
  • atan2(x) - tagastab radiaanides esitatud argumendi arkustangensi
  • int(x) - tagastab argumendi täisosa
  • log(x) - võtab argumendist naturaallogaritmi
  • sqrt(x) - tagastab argumendi ruutjuure
  • rand(x) - genereerib juhusliku arvu vahemikus 0 kuni 1
  • srand(x) - tekitab rand() funktsioonile juhusliku alge

Valikulause ja tingimused

if-else

Toome näite valikulausest, kus skript genereerib juhusliku arvu ja teatab, milline see arv on

#!/usr/bin/awk -f
BEGIN {
srand()
x = rand ()
if ( x < 0.5 )
	{
	print x " on vaiksem poolest"
       }
else
	{
    	print x " on yle poole"
	}
}

Tingimusi seatakse võrdlustehte abil:

  • < - väiksem
  • > - suurem
  • == - võrdne
  •  != - mittevõrdne
  • <= - väiksemvõrdne
  • >= - suuremvõrdne

Võrdlustehete tulemustega saab sooritada loogilisi tehteid:

  •  ! - eitus
  • && - ja
  • || - või

Näiteks kirjutame skripti, mis ütleb, kas juhuslik arv on 0.2 < x < 0.8

#!/usr/bin/awk -f
BEGIN {
srand()
x = rand ()
if   ( x > 0.2 && x < 0.8 ) print " 0.2 < " x " < 0.8"
}

Kui tingimusele järgneb vaid üks käsk, siis võib loogad ära jätta ja isegi kirjutada käsu tingimuse järele samale reale.

Võimalik on ka kasutada tuntud

(tingimus) ? (lause 1) : (lause 2)

sarnast konstruktsiooni.

#!/usr/bin/awk -f
BEGIN {
srand()
x = rand ()
print ( x < 0.5) ? x " on vaiksem poolest" : x " on yle poole" 
}

Korduslause

Kordus sooritab tegevust mitu korda. Kõik näited väljastavad arvud ühest viieni.

While kordus

#!/usr/bin/awk -f
BEGIN { x=1
while ( x <= 5 ) {
	print x
	x++
}
}

Do kordus

#!/usr/bin/awk -f
BEGIN { x=1
do {
	print x
	x++
} while ( x <= 5 )
}

For kordus

#!/usr/bin/awk -f
BEGIN {
for (x=1; x <= 5; x++) {
	print x
}
}

Massiiv ja paisktabel

Massiivi on otstarbekas kasutada, kui on vaja tegelda paljude samalaadiliste väärtustega.

Näiteks defineerime massiivi m ja trükime korduse abil välja tema kõigi elementide väärtused:

#!/usr/bin/awk -f
BEGIN {
i=0
m[0] = "mina"
m[1] = "sina"
m[2] = "tema"
while ( i < 3 ) {
      print m[i]
   }
}

Paisktabel

Paisktabel on massiiv, mille indeksiks on string. Defineerime massiivi ja trükime välja teise elemendi.

#!/usr/bin/awk -f
BEGIN {
m["meie"] = "mina"
m["teie"] = "sina"
m["nemad"] = "tema"
print m["teie"]
}

Sisendi ja väljundi kasutamine

Skript loeb andmeid sisendisse või klaviatuurilt ning kirjutab väljundisse või ekraanile.

#!/usr/bin/awk -f
BEGIN {
print "sisestage oma eesnimi"
getline enimi < "-"
print "sisestage oma perekonnanimi"
getline pnimi < "-"
print ( enimi == pnimi) ? "Teie ees- ja perekonnannimi on ühesugused"\
: "Teie ees- ja perekonnanimi ei ole ühesugused"
}

Rida jätkav kaigas peab olema viimane sümbol real.

Eeldades, et tekstifailis on kahel real vastavalt eesnimi ja perekonnanimi, käivitatakse skript selliselt

bash~$ cat tekst | skript.awk

Soovides skripti tööosa kasutada, tuleb kirjutada selline skript, kusjuures andmefailis on ühel real tühikuga eraldatud ees- ja perekonnanimi.

#!/usr/bin/awk -f
{ 
print ( $1 == $2 ) ? "Teie ees- ja perekonnannimi on ühesugused"\
: "Teie ees- ja perekonnanimi ei ole ühesugused"
}

Kui andmetega ridu on mitu, teostatakse kontroll iga reaga.

Faili lugemine ja kirjutamine

Loeme failist andmed ja kirjutame nummerdatud read teise faili.

#!/usr/bin/awk -f
BEGIN { i=1
while ( (getline rida < "lahteandmed" ) > 0 )
	{
		 print i ". " rida > "toodeldudandmed"
		 i++
	}
}

Korduse tingimuseks olev käsu getline lõppkood on nullist suurem seni, kuni midagi failist lähteandmeid lugeda on.

Süsteemi programmi väljundi lugemine ja sisendisse saatmine

Omistame muutuja aeg väärtuseks programmi date väljundi ning saadame muutuja aeg väärtuse programmi cat sisendisse.

#!/usr/bin/awk -f
BEGIN {
"date" | getline aeg
print "Süsteemi aeg on " aeg
print aeg | "cat"
}

Kirje ja väli

Awk käsitleb sisendit struktureerituna. Vaikimisi nimetakse kahe järjestikuse reavahetuste vahele jäävat kirjeks. Vaikimisi on jaotatud rida väljadeks tühikute või tabulatsioonimärkide kohalt.

Programmi tööosas saab väljade poole pöörduda muutujate $1, $2, $3, $4 jne abil. Muutuja $0 väärtuseks on terve kirje.

Sõltuvalt välja sisust saab teha nendega tehteid. Toome näite, kus leiame tulpadena esitatud lähteandmete ridade summad

bash~$ cat arvud
1 3 4 5
4 6 2 4
1 5 9 4
2 4 5 5

ning skript leia.summad.awk on seesugune

#!/usr/bin/awk -f
BEGIN   { print "Leiame ridade- ja kogusumma" }
            {  summa = $1 + $2 + $3 + $4
               print $1 " + " $2 " +  " $3 " +  " $4 " = " summa
               kogusumma = kogusumma + summa
            }
END     { print "Kogusumma = "  kogusumma }

Siin on esitatud kolm võimalikku skripti osa: algusosa, tööosa ja lõpuosa, kusjuures neile osadele vastavad käsud kirjutatakse loogadesse. Ühes osas kasutatud muutujatel on teises osas sama väärtus (nt kogusumma).

Käivitame skripti:

bash~$ cat arvud | leia.summad.awk
Leiame ridade -ja kogusumma
1 + 3 + 4 + 5 = 13
4 + 6 + 2 + 4 = 16
1 + 5 + 9 + 4 = 19
2 + 4 + 5 + 5 = 16
Kogusumma on 64

Kirje- ja väljaeraldusmärkide ümbermuutmine

Mõnel juhul on vajalik andmeid ridadeks ja väljadeks jagavaid märke muuta. Vastavalt saab muuta ka väljundis kasutatavaid rea- ja väljaeraldusmärke.

Seda saab teha näiteks defineerides skripti algusosas üle vastavate muutujate väärtused:

  • FS - sisendi välja eraldaja (ingl. k. field separator)
  • RS - sisendi kirje eraldaja (ingl. k. record separator)
  • OFS - väljundi välja eraldaja (ingl. k. output field separator)
  • ORS - väljundi kirje eraldaja (ingl. k. output record separator)

Kui välja eraldajaks on tühik, siis vastab see Awki vaikimisi toimimisele; sarnaselt on reavahetuse märgi ja kirje eraldajaga. Seades eraldajaks mõne teise sümboli, eraldatakse kirjet või välja selle sümboli kohalt. Kui eraldajaks on enam kui üks sümbol, käsitletakse eraldajat regulaaravaldisena.

Lisaks saab kasutada Awkiga seotud muutujaid

  • FILENAME - sisendfaili nimi
  • NF - käesoleva kirje väljade arv (ingl. k. number on fields)
  • NR - kirje absoluutne järjekorra number (ingl. k. number of the record)
  • FNR - kirje suhteline järjekorra number; on vajalik mitme sisendfaili kasutamisel

Toome näite nende muutujate kasutamise kohta. Algandmed on sellised

Priit
Elva
Jüri 15
tel. 15 123

Mart
Jüri
Elva 15
tel. 12 3 15

Peeter
Tartu
Telefoni 5
tel. 12 34 56

Nad soovitakse esitada nii, et ühte linna puutuv oleks ühel real, read oleks nummerdatud ning kõige lõppu kirjutataks algandmete faili nimi

$ ./skript.awk sisend.txt
1:Priit:Elva:Jüri 15:tel. 15 123
2:Mart:Jüri:Elva 15:tel. 12 3 15
3:Peeter:Tartu:Telefoni 5:tel. 12 34 56
andmefail: andmed

Sellise töö teeb ära selline skript

#!/usr/bin/awk -f
BEGIN {FS="\n"; RS="\n\n"; OFS=":"; ORS="\n"}
	{ print NR, $1, $2, $3, $4 }
END { OFS=" "; print "andmefail:", FILENAME }

Adresseerimine

Regulaaravaldistega saab määrata, millistele sisendi ridadele tööosa käsud rakenduvad. Toome näite, kus tegeldakse vaid Tartu linna temperatuuride ööpäevase keskmistamisega. Algandmed failis temperatuurid on sellised

linn   kp         6:00  12:00  18:00  24:00
Tartu  19/4/2000  13.6  15.1   15.8   14.0
Valga  19/4/2000  11.6  12.1   12.8   11.0
Tartu  20/4/2000  14.6  18.4   13.5   13.3
Valga  20/4/2000  13.4  15.7   15.1   14.3
Tartu  21/4/2000  16.6  21.1   19.3   17.1
Valga  21/4/2000  15.7  19.4   17.6   15.5
Tartu  22/4/2000  15.6  23.6   22.6   15.4
Valga  22/4/2000  13.5  16.2   14.7   15.3
Tartu  23/4/2000  17.3  19.4   21.4   12.3
Valga  23/4/2000  18.2  16.9   13.8   15.2

Skripti tööosa tegeleb vaid nende andmeridadega, mis klapivad regulaaravaldisega /Tartu/

#!/usr/bin/awk -f
BEGIN { print "Tartu õhutemperatuuri ööpäevane keskmine"
	printf ("Teostati neli mõõtmist: 6:00 12:00 18:00 24:00\n\n")
	print "koht   kuupäev   mõõtmistulemus"
	}
  /Tartu/ { keskmine = ( $3 + $4 + $5 + $6 ) / 4
            printf ("%s  %s  %2.2f\n", $1, $2, keskmine)
	  }
END   { printf ("\nMõõtmisi teostasid Priit ja Mart\n") }

Käivitamisel saame järgmise tulemuse

bash$ cat temperatuurid | tartu.keskm.temp.awk 
Tartu õhutemperatuuri ööpäevane keskmine
Teostati neli mõõtmist: 6:00 12:00 18:00 24:00

koht   kuupäev   mõõtmistulemus
Tartu  19/4/2000  14.62
Tartu  20/4/2000  14.95
Tartu  21/4/2000  18.52
Tartu  22/4/2000  19.30
Tartu  23/4/2000  17.60 

Mõõtmisi teostasid Priit ja Mart

Funktsiooni defineerimine

Funktsioonid defineeritakse väljaspool skripti osasid sellise süntaksi alusel

function funktsiooni.nimi (arg1, arg2, arg3) {
     laused
     return res1 res2
}

Esitame eelmise punkti viimase näite nii, et keskmine temperatuur leitakse funktsiooni abil

#!/usr/bin/awk -f
BEGIN { print "Tartu õhutemperatuuri ööpäevane keskmine"
	printf ("Teostati neli mõõtmist: 6:00 12:00 18:00 24:00\n\n")
	print "koht   kuupäev   mõõtmistulemus"
	}
function kesktemp (m1, m2, m3, m4) {
	keskmine = ( m1 + m2 + m3 + m4 ) / 4
	return keskmine
	}
  /Tartu/ { printf ("%s  %s  %2.2f\n", $1, $2, kesktemp($3, $4, $5, $6)) }

END   { printf ("\nMõõtmisi teostasid Priit ja Mart\n") }

Tööosa kasutamise näited

SQL dump faili tükeldamine

Olgu lähtepunktiks SQL dump fail sql.sql, mis sisaldab sarnaseid INSERT lauseid

 INSERT INTO inimesed VALUES (1, 'Mart', 'Kask', 38709067463);
 INSERT INTO inimesed VALUES (2, 'Priit', 'Kask', 38406087465);
 INSERT INTO inimesed VALUES (3, 'Laa', 'Lee', 38701304463);
 INSERT INTO inimesed VALUES (4, 'Koosinus', 'Pii', 31415926535);
 ...

Soovides jagada suure faili tükkideks nii, et iga tükk sisaldab 100 kirjet tuleb näiteks öelda

 $ awk 'BEGIN{RS="\nINSERT INTO inimesed ";} { i=i+1; j= i % 100 ; if ( j == 1 ) fi=fi+1; if ( i > 1 ) printf \
 ("%s", "INSERT INTO inimesed ") >> "fn_"fi; print >> "fn_"fi ; printf ("%d\n", i); system ("sleep 1") }' sql.sql

Skriptis on näha tööosas selliste konstruktsioonide kasutamist

  • aritmeetilised tehted ja võrdlused
  • väljundi formateermine ja faili suunamine
  • süsteemse käsu andmine

Tabulatsioonimärkidega edaldatud väljade info kättesaamine failist

$ awk 'BEGIN {FS="\t"; OFS="\t"} {print $1,$2}' large.txt > small.txt

Awkiga sisendi jooksev lugemine ja töötlemine

Jooksvalt töötav awk wrapper e vahekiht mis loeb ühelt töötavalt mingilt programmilt väljundit ja käitub vastavalt käske

Stardime programmi ja suuname ta väljundi wrapper skriptile, jätame mõlemad taustale tööle

/root/bin/programm | /root/bin/wrapper.sh &

Skript on ise selline, näiteskriptis loeb ta väljundist vaid kahte numbrit "1" ja "2". Esimesel juhul saadab ta maili generaator ok, teisel juhul väljastab teate rike ja saadab emaili generaator töötab.

#!/usr/bin/awk -f
{
if ( $3 != "1" )
        {
         print " systeem toimib"
         "echo \"generaator ok!\" | mail -s \"generaator ok!\" email@asdf.ee" | getline current_time
        }
else
        {
         print " rike"
          "echo \"generaator t22tab!\" | mail -s \"generaator t22tab!\" email@asdf.ee -c  email@asdf.ee"" | getline current_time
        } 
}

Lingid

http://www.ee.ucl.ac.uk/~hamed/misc/awk1line.txt Awk näidete kogumik

https://wiki.itcollege.ee/index.php/Awk

http://www.cyberciti.biz/faq/bash-scripting-using-awk/ hea lühike manual