juuli 09, 2015

Regulaaravaldised ja grep I

I Lihtsalt GREP
















Hallo jälle!

Et mitte jääda üldsõnaliseks, võtaksin ette lubatud regulaaravaldiste uuringu ja kasutuse *nix töövahendi grep abil.
Selle lühendi päritolust huvitunu vaadaku minu eelnevat lugu 'Alguses oli ed'.
grep pärineb editori ed käsust g/re/p
ehk global, regular expression ja print.
Nii see asi üldjoontes toimibki, grep võtab sisendist
järjest ridu ja valib neist sobivad välja. Need väljastatakse ehk prinditakse.
Sobivus tehakse kindlaks regulaaravaldise põhjal.

Miks grep/ miks regulaaravaldised?

* Õnneks (fännide jaoks, kuhu ka mina kuulun) / kahjuks (mittefännide) jaoks on see arvutiasjanduse teoreetilise poole jaoks üsna oluline ja annab kursuse „Automaadid, keeled, translaatorid” läbimisel TÜ-s 6 arvestuspunkti.
Seal, tõsi, peab muid asju ka lisaks uurima.
See teooria algab aga üldjoontes sellest, et kirjutatud skriptiread analüüsitakse läbi regulaaravaldisi kasutava vahendi abiga.
Umbes sama, nagu Maxwelli võrrandid füüsikule, arvan mina füüsika vastava kadalipu läbinuna.
Võib-olla ei lähegi teil seda tööalasel reinkarneerumisel enam vaja, aga ei kujuta ette füüsikut, kes mõistet Maxwelli võrrand ei tea. Selles mõttes muidugi, et ta teab, et on teadnud.

*Õnneks (fännide jaoks) on Unix majandamine/sättimine põhinev tekstifailidel ja nendest kasulike terade väljasõelumisel on regulaarsed avaldised suureks abiks. Enamasti küll kõige lihtsamas vormis, kus „avaldise” asemel on lihtsalt otsitav sõne ja otsitakse tähttähelist ehk literaalset vastavust.

* Programmeerijate jaoks aga on iva vahest selles, et nende endi kokkukirjutatud koodilasu seast vajalikke juppe üles leida ja vajadusel ka modifitseerida. Tekstifailid on endiselt programmeerijate põhiline andmebaas ja selle andmebaasi haldamise üheks töövahendiks on regulaaravaldisi kasutavad vahendid. Saab ka ilma läbi, nagu foorumitestki selgub. Mõni armastab, mõni vihkab.
Nagu ikka selles maailmas.

* Aga TEGELIKULT on need minu jaoks on need päris huvitavad mõtteharjutused. Ning sellest piisab täiesti.
Tegelikult on see jutt nende asjade suurest kasust lihtsalt teadvale valele pikemate jalgade allakasvatamiseks. Jah, akadeemilisi punkte saab, aga saab ilma ka läbi. Jah, võib tööintervjuulgi kõvem tegija olla – aga sealgi võivad küsitlejate staatuses olla need, kellel on selline eelarvamus kusagil juba pähe istutatud, et regex on HEA ASI.
Sudokudega jahmerdamisel ma ei pea hakkama põhjendama, miks selline asi nii hiiglama hea on.

Jamie Zawinski: (üteluse väljastamise ajal Netscape insener)
Mõned inimesed mõtlevad mingit probleemi nähes -
„Tean, et hakkan kasutama regulaaravaldisi.”

Nüüd on neil kaks probleemi.


Kuidas see kõik algas?

1943 avaldasid Warren McCulloch ja Walter Pitts artikli
A logical calculus of the ideas immanent in nervous activity”.
ON võrdlemisi raske aru saada, et siin võiks üldse pistmist olla millegiga, millest saavad tulevikus regulaaravaldised. Tegemist on neuroteadusega ja neurovõrke kirjeldadakse, nagu hiljem selgus, lõplike automaatide keeles.
1955 aga arendas Stephen Kleene teooriat edasi.
Inimese moodi saab sellest lugeda hm. siiski lugeda wikist.

1968 ilmutas Ken Thompson artikli, kus kirjeldas (ja realiseeris) algoritmi, kus regulaaravaldiste põhjal saab kiiresti teada, kas vastavus on leitud, või mitte etteantud teksti sees. http://www.fing.edu.uy/inco/cursos/intropln/material/p419-thompson.pdf

Algoritm oli efektiivne ja siit see asi algas, sest seda oli suhteliselt lihtne realiseerida ning ka praktiline.
Ken Thompson realiseeris selle editoris ed – vt. 'Alguses oli ed' käsuna g/re/p.
Aga kõiki regulaaravaldisi grep ei realiseerinud. Ainult „põhilisi”.
Praegusel ajal kajastub see selles, et grep vaikimisi käivitub „baasrezhiimis”, kus kõiki asju ei ole sisse kirjutatud (grep – G).
Laiendatud grep, extended grep või egrep valmis Alfred Aho kirjutatuna 1979.
See on nüüd see grep, millega põhiliselt peaksime tegelema. Kõik Kleene poolt sissekirjutatud võimalused on egrep-s olemas.
Käivitage linux-is grep -E – see on sama, mis egrep.

Kuid ilma PERL programmeerimiskeeleta ei oleks regulaaravaldised iialgi muutunud kultuseks. Perlis on regulaarne avaldis kultusobjekt ja
Larry Wall lisas siia mitmeid vahepeal leiutatud võimalusi, mis enam Kleene poolt kirjeldatud süsteemi ei kuulu. Nii ongi „laiendatud” regulaaravaldiste järel perl mõjutusel tekkinud veel rohkem laiendatud regulaarsed avaldised.
Tähejoru PCRE tähistab näiteks Perl Compatible Regular Expression-i, Perl-iga ühilduvat, mitte enam nii regulaarset avaldist.
.... Olgu. Aitab.
Ajaloo eest olge tänulikud põhiliselt järgmisele saidile:
http://blog.staffannoteberg.com/2013/01/30/regular-expressions-a-brief-history/
(ja muidugi wikile, keda ei viitsigi alati tänama hakata).

Hakkame greppima

Hakatuseks unustame regulaarsed avaldised peaaegu üldse ära.
Kui etteantud sõne sisaldub selles reas, siis on vastavus leitud. Lihtne literaalne ehk tähtäheline otsing niisiis:
'Arno' klapib reaga 'Kui Arno isaga ...'

Lihtsaks proovimiseks sobikski lõiguke „Kevadest” ja sealt greppimise näiteid.
Üheks võimalikuks nuhtluseks võib olla täpitähtede kodeering siin.
Ma olen üritanud püsida utf-8- piires. Aga mine sa seda õ-d tea!
Toon selle siin ära, kopeerige, peistige see klassika oma *nix-isse ja katsetage ja tippige kahtlased täpilised ümber, kui ei tule midagi nende täppidega grepis välja.
Või kirjutage midagi, mis paremini peale läheb ja greppige siis ära.
Tehke omale Linuxis kataloog grepp.
Sinna fail 'kevade' järgmise krestomaatilise sisuga:



Kui Arno isaga koolimajja jõudis, olid tunnid juba alanud.
Kooliõpetaja kutsus mõlemad oma tuppa,
kõneles nendega natuke aega, käskis Arnol olla hoolas ja ja seadis ta siis pinki ühe pikkade
juustega poisi kõrvale istuma.
Siis andis kooliõpetaja talle raamatust midagi kirjutada, ja Arnol ei olnud
nüüd enam aega muule mõtelda.
Ta võttis tahvli ja hakkas kirjutama.
Kui ta oli kirjutanud umbes paar rida, kummardus pikkade juustega poiss tema kõrva juurde ja küsis sosinal:
"Mis koolmeister ütles, kui te tema toas olite?"



Ja saabki hakata greppi uurima.
Kaua aretatud töövahendina on tal mitmeid käivitamise võimalusi, katsun need enam-vähem kirja panna (mitte manuaali täpsusega).

Grepi kasutuse 2 viisi

I

grep -VÕTMED REGULAARAVALDISED-MUSTRID FAILID/VÕI FAILIMUSTRID

II

xxx xxxx xxxx ... | grep -VÕTMED MUSTRID | ...

käskude vahel on siis konveieri / toru sümbol '|'

Kolmas viis on ka, seda ei tasu grafomaania all mittekannatavatel kasutada (või siiski, ainult et
grepitavaks võiks olla mõne teise kasutaja terminal?)

III

grep -VALIKUVÕTMED MUSTRID

Nüüd saate oma kirjanduslikke võimeid proovile panan. Teie loomingulise palangu lõpetab
sellel puhul Ctrl C.
Äraseletatult grep kuulab standardset sisendit, milleks on terminal ja selle taga istuv alustav linuxifänn... ja kui saadetakse signaal kuulamine lõpetada (Ctrl C), siis grep seda ka teeb.
Kui aga ei saadeta?

Detailsem selgitus

Alustame valikuvõtmetest. Nagu ikka Linux puhul võivad võtmed olla pikalt välja kirjutatud, või lühidalt.
Lühidalt tähendab ühe tähena.
Suured ja väiksed tähed on VÄGA erinevad valikud grep puhul. Võtmeid saab kirjutada kokku, näiteks kaks võtit -Ec koos, aga ka lahku, näiteks -E -c ...
Iga võtme jaoks on manuaalis toodud (man grep ) alati ka pikk variant. Näiteks ülaltoodud -E pikaks variandiks on –extended-regexp. Mõne pika variandi puhul lühikest ei ole --color ntx.
{
Siis näitab grep värvilist tulemit kohtades, kus oli klapp (match), kui pikalt kirjutada --color=auto,
siis näitab ekraani peal värvi, muude suunamiste puhul võtab grep värvid välja.
}

Kui te kirjutate mingit kesta skripti, võib pikkadel võtmetel olla mõte, käsurealt käivitusel tõenäoliselt mitte.
Mustrid (tavaliselt üks muster ) kohal peaks olema mingi hulk regulaaravaldisi. Kui neid rohkem tahetakse sisestada, siis iga mustri ees peaks olema uuesti kirjutatud võti -e.
Näiteks:

grep -e 'Kui' -e 'poiss' kevade

Mustreid võib koguni võtta failist, iga uus muster eraldi realt -f võti ütleb siis, et MUSTRID kohale võiks kirjutada failinime, kust neid mustreid võetakse.

Failide kohal võib olla üks või mitu faili.
Võib olla ka üldine muster.
*.c tähistab kõiki faile, mille lõpus on laiend .c
Aga failide mustrid ei ole regulaaravaldised.
Neist natuke ka edaspidi, aga võtmesõned globbing, bash expansion googlist aitavad selgitada seda, kuidas failinimed grep käsureale maabuvad.

Regulaaravaldiste mustrid tuleb kaitsta ühekordsete jutumärkidega. Sel juhul annab kestaprogramm bash selle mustri sisu programmile grep ilma modifitseerimata edasi.
Failimustreid jälle ärge kaitske mitte mingisuguste jutumärkidega - te ei saa kõiki .c laiendiga faile '*.c' kasutades.
Erandiks oleks vajadus kasutada keskkonnamuutujaid – siis kirjutage kahekordsed jutumärgid ja nende sisse siis see / need keskkonnamuutujad.
Päris ilma jutumärkideta võib ka proovida, aga varem või hiljem viib see ootamatute tulemiteni, asjad ei tööta nii, nagu tahetakse.

Grep kasutusvallad ilma regulaaravaldiste regulaarteadmisteta:

Erigreppide uputus:

80% juhtudel teame täpselt sõnet, mida otsime. See tulebki ühekordsete jutumärkide vahele kirjutada.
Aga oh häda! Mõnikord sisaldab otsitav sõne regulaaravaldiste jaoks eri ehk metasümboleid.

Kuna selliseid erijuhte on nii palju, siis on selle jaoks vanal ajal leiutatud eraldi grep nimega
fgrep. fgrep jaoks ei ole mustrid mingid regulaaravaldised, vaid tavalised sõned / stringid.
Samamoodi grepi täiustuseks on leiutatud egrep, mis jälle neid regulaaravaldisi rohkem tunneb ning lõpuks rgrep,
mis oskab käia mööda alamakatalooge.
Ühel ilusal päeval arvati siiski, et neid greppe saab isegi Linuxi jaoks natukene liiga palju (väikesed tööriistad, mis teevad praktiliselt väga sarnast asja ja mis Unixi filosoofiale erandlikult lõpuks jälle kokku liideti).
Tänasel päeval öeldakse egrep, fgrep, rgrep kasutuste kohta 'deprecated' (moest läinud) ja kasutatakse selle asemel
grep -E
grep -F
(jah, -F, mitte -f, mis tähendab failist lugemist).
grep -r
(ja väga üllatuslikult ka grep -R tähistab sama, ülejäänud juhtudel on väike ja suuretähelised võtmed VÄGA erinevad).
Mida -r/R, -E võtmed teevad, sellest allpool.
grep -F MUSTRID / või grep -Ff MUSTRIFAIL lihtsalt võtab sõnesid tähttähelt ja otsib ridades nende vastavusi.
Olgu näiteks meil teada, et firma arendusosakonnas töötavad Toomas, Paul, Juhan ja Kalle.
Mustrifailis on read Toomas Lepp, Paul Kask, Juhan Kuusk ja Kalle Mänd.
Ühe käsuga saame välja greppida kõik read otsitavates failides, kus neist kodanikest nende pika nimega juttu on.

Mida aga tulemustega teha?

Lihtsalt read kusagil failis / ekraanil ei pruugi anda palju infot. Et lisada faili nimi, kust mustriga klappiv rida pärit, on olemas võti -H.

grep -H 'Arno' kevade

kevade:Kui Arno isaga koolimajja jõudis, olid tunnid juba alanud.
kevade:kõneles nendega natuke aega, käskis Arnol olla hoolas ja ja seadis ta siis pinki ühe pikkade
kevade:Siis andis kooliõpetaja talle raamatust midagi kirjutada, ja Arnol ei olnud

Kui faile on 1, ei lisata niikuinii failinimesid, aga kui neid on rohkem, lisatakse failinimi vaikimisi ja neist saab lahti ainult -h võtit teades.

cp kevade kevade1
grep 'Arno' *
grep -h 'Arno' *


Et lisada rea numbrit, on olemas võti -n.
Tõsi, lihtsam on lihtsalt rea numbri lisamiseks cat -n kasutamine.

grep -n '' kevade >kevade1
cat kevade1

(Tühi muster '' klapib iga reaga (aga tühi fail, kus polegi mustreid, ei klapi millegagi).

Seda võtit tasub kasutada failile reanumbrite juurdelisamisel!
Ja siis ka, kui mõnes programmijupis on vaja teada, mis real mingi 'jama' paikneb.

Et väljastada AINULT faili nimi, kust rida pärit, on võti -l, aga -L väljastab sellise faili nime, kus
klappi MUSTRITEGA ei leitud.

grep -l 'Arno' *

kevade
kevade1

grep -L 'Arno' *

grep -L '5' *
kevade

Äkki tunneme huvi vaid sõna / nime 'Arno' vastu (siin küll pigem vastupidi, ka Arno eesti keele käänetes on vajalik ära tunda)?
Väike, KINDLASTI väike w aitab siis.

grep -w 'Arno' kevade

Suur võti -W on grepile tundmatu ja greppija saab korvi:

Mõnikord tahame teada, palju ridu meie mustritega klapib.
-c loeb need READ üle. Mitte klappide arv, see võib suurem olla!

grep -wc 'Arno' *

kevade:3
kevade1:3

Huvitav, kuidas siit võiks saada ridade koguarvu?
Appi tuleb võtta 'Alguses oli ed' st tuttav awk.

grep -ch 'Arno' * | awk '{ sum += $1 } END { print sum }'

6

Väikese vaevaga saab siit nüüd rea, mis mingi programmeerimse projekti puhul loeb ära,
mitu rida arendajad tööd on teinud.

grep -rch --include='*.c' --include='*.h' '' . | awk '{ sum += $1 } END { print sum }'

mõõdab ära C projekti seas olevate koodiridade arvu, kui olete selle projekti ruudukataloogis.

Selgitusi: -r - programm käib mööda antud kataloogi '.' ja tema alamkatalooge mööda.

Iga kord, kui grepi failide reas kohtame mingit kataloogi, ütleb võti -r, et mine selle kataloogifaili sisse ja hakka seal samamoodi faile läbi vaatama. Kui include-t ei ole, vaadatakse läbi kõik failid.
Kui '.' asemele oleksime pannud * sümboli, siis kõigepealt oleks bash
asendanud '*' selles kataloogis olevate kõikide failide loendiga (nii see Unix tehtud on, bash teeb enne sellised asendused ära) ja siis oleks grep need failid üle vaadanud. Kataloogifailides oleks välja valitud *.c ja *.h failid, aga jooksvas kataloogis vaadatakse üle KÕIK failid, mis sellest, et
--include keelaks nagu selle ära.

Mõnikord huvitab meid see, et grep teeks tööd ainult tekstifailidega (enamasti nii ongi). Selleks PEATE eraldi võtme panema, -I.
Nüüd mistahes failide korral, kus Unix arvab, et need on binaarsed, grep lõpetab tegevuse (0 vastavust).
Aga kui UNIX on veendumusel, et tegemist ikkagi on tekstifailiga, loetakse seal oleva binaarsest jurast sõltumatult see greppi sisse ja teatatakse absurdsetest tulemitest, näiteks ridade arvust binaarfailis.
Nii et -r võtme korral on vaja otseselt ära märkida KATALOOGID, kust grep hakkab faile otsima ning alati ka --include=MUSTER abiga sobivad laiendid, ilma selleta võite saada ootamatuid tulemusi.

EITUS

Eituseks kasutage võtit -v.

grep -v 'Arno' kevade

kevade:Kooliõpetaja kutsus mõlemad oma tuppa,
kevade:juustega poisi kõrvale istuma.
kevade:nüüd enam aega muule mõtelda.
kevade:Ta võttis tahvli ja hakkas kirjutama.
kevade:Kui ta oli kirjutanud umbes paar rida, kummardus pikkade juustega poiss tema kõrva juurde ja küsis sosinal:
kevade:"Mis koolmeister ütles, kui te tema toas olite?"
kevade:

Klappimiste arvust veel kord

Teame nüüd, et
grep -c 'Arno' kevade
mõõdab ära, mitmel REAL esines nimi Arno. Vastus on, et Kevade esimeses lõigus mainiti teada 3 korda.
Aga mis siis saab, kui Arnot on samal real MITU korda?

Selle ülelugemiseks saab kasutada -o võtit.
-o kirjutab eraldi reale välja täpse klappimuse, täpse vastavuse ja mitte midagi muud.
See paistab üsna totter olevat, sest kui nüüd teha

grep -o 'Arno' kevade,

saame 3 -le reale sellesama Arno.
Arno
Arno
Arno

See võiks omada mingit mõtet siis, kui me tõesti otsime mingite kavalate regulaaravaldiste abil erinevaid vastavusi tekstis?

Kuid isegi sellise tähttähelise klapi korral on sellel totrusel kasulikke omadusi!
Oletame näiteks, et tahame teada, mitu korda 'Kevades' on kasutatud tähte 'k'.
Piirdume esialgu selle esimese lõiguga:

grep -oi 'k' kevade | wc -l

annab selle vastuse - meie tekstis oli 26 k-d.
Võti -i teeb otsingu 'tõstutundetuks'.

Miks mitte kasutada tähtede lugemist selleks, et vaadata, kui keskmiselt hästi Oskar Luts kirjutas? Kui kõikide häälikute keskmine sagedus on paigas, siis on tegemist täieliselt keskmise Eesti tekstiga ja me võime rahvakirjanikuga rahule jääda.

Harjutamiseks kasutame ka ühte lihtsaimat regulaaravaldist:
'[a-z]' - nurksulgudes on toodud loetelu lubatud tähtedest, sidekriips tähistab vahemikku, praegu siis kõiki tähti. Kui teil on õieti paigas kasutatav kooditabel, on kõik hästi ja sinna sisse mahuvad ka täpitähed. Võib juhtuda, et kavalam oleks kirjutada otse välja täpilised juhtumid. a-z sisaldab kindlasti ladina tähti, aga eraldi välja kirjutada tuleks ?kõik (ka väiksed ja suured täpitähed).
Seega selline regulaaravaldis: '[a-zõäöüÕÄÖÜ]'.
Kolmas võimalus alfabeetiliste tähtede nimetamiseks oleks spetsiaalne nimi:
'[:alpha:]'

Nüüd käsk

grep -oi '[a-z]' kevade | sort | uniq -c | sort -nr
VÕI
grep -oi '[a-zõäöüÕÄÖÜ]' kevade | sort | uniq -c | sort -nr
VÕI
grep -oi '[[:alpha:]]' kevade | sort | uniq -c | sort -nr

loeb ära ja järjestab erinevad tähed (suurtähtede väljaloopimine jäägu mõneks teiseks korraks!) selles lõigus.
Saame teada, et Lutsul oli tähtede sagedusjärjestus Kevade esimeses lõigus selline:
62 a
40 i
33 s
31 e
29 u
28 t
24 l
23 o
23 k
19 d
17 n
17 m
17 j
14 r
10 p
9 õ
7 g
5 ü
4 v
4 h
3 K
3 A
2 b
1 T
1 S
1 M
1 ä
'aiseutlokdnmj...' võiks olla eesti keele tähtede umbkaudne sagedusjärjestus, seda ÜHEL UNIXI käsureal saavutatuna.
Kui annaksite mulle ette terve 'Kevade', hindaks juba täpsemini tähtede sageduse ära.
Ja nii saab mõõta ka sõnu, näiteks võiks see meetod kiiresti anda vastuse küsimusele, kes kirjutas
'Talve', seda kõike ainult UNIX kesta vahenditega. Edaspidi ka sellest ja kust see rida, mis tähtede sagedusi mõõtis, on võetud...

Nii et see kärbseid ja täisid täis 'Ida Aafrika' maakoht tasapisi muutub päris põnevaks programeerimise laboratooriumiks!
Kontroll: Nagu võib teada saada saidilt
on eesti keele tähtede sagedus selline:
A, E, I, S, T , L, U, N, K, M , O, D, R, V, G , H, J, P, Ä, Õ , B, Ü, Ö, F, Š , Ž, C, W, Y, Z , X, Q.

Nagu näete, saime PÄRIS HEA KLAPI, seda vaid ühe Unix käsurea abiga!!
Luts on täiesti keskmine Eesti kirjanik, ei rohkem ega vähem.
Ja Arno isaga veel väga, väga kaua aega jõuavad kooli alles siis, kui tunnid on juba alanud (ja regulaarsed avaldised läbi võetud).