Otsikoiden haku: grep

Tutkitaan ensin tiedostoa hiukan otsikkotasolla. Huomasimme, että tiedostossa on otsikot merkitty tyyliin = Amsterdam =, siis rivin alussa = ja välilyönti, sen jälkeen otsikko, ja rivin lopussa välilyönti ja =.

Etsitään kaikki tällaiset rivit. Erinomainen työkalu erilaisten hahmojen etsimiseen tiedostoista on grep. Tämä komento toimii tähän tapaan:

grep hahmo tiedosto

jossa hahmo on kuvaus siitä, millaisia rivejä haluamme löytää, ja tiedosto on tiedoston nimi. Voit värittää osumat näin (joissain ympäristöissä tämä onkin jo oletuksena päällä):

grep --color hahmo tiedosto

Voisimme siis etsiä vaikkapa kaikki sanan "lapio" esiintymät näytepalasta tähän tapaan:

grep --color lapio osa.txt

Tämä etsii täsmälleen merkkijonoa "lapio", juuri näin pienillä kirjaimilla kirjoitettuna — kokeile! Mutta miten voisimme löytää otsikot? Esimerkiksi pelkästään merkkijonon "= " tai " =" etsiminen ei auta, kun tiedosto on täynnä väliotsikoita ja muuta koodia, joissa näitä esiintyy.

Säännölliset lausekkeet

Onneksi grep mahdollistaa etsittävän hahmon kuvaamisen niin sanottujen säännöllisten lausekkeiden avulla. Opimme tässä moduulissa muutaman esimerkin siitä, mitä säännöllisillä lausekkeilla voi kuvata, mutta kun pääset alkuun, kannattaa opiskella aiheesta itse lisää!

Säännöllisillä lausekkeilla voidaan kohdistaa haku esimerkiksi rivin alkuun tai rivin loppuun. Merkki ^ tarkoittaa hakemista rivin alusta. Voisimme etsiä esimerkiksi kaikki rivit, joiden alussa esiintyy sana "Tietokoneen" (juuri näin kirjoitettuna) tähän tapaan:

grep '^Tietokoneen' osa.txt

Kirjoitimme tässä haettavan hahmon '-merkkien sisään, jotta Linuxin komentotulkki ei yritä itse tulkita erikoismerkkejä, vaan koko rimpsu ^Tietokoneen menee varmasti sellaisenaan grep-komennolle yhtenä parametrina. Tähän on hyvä tottua jo nyt; hyvin pian tämä on todella tarpeen.

Komento näyttää löytävän juuri sitä, mitä olettaisikin, esimerkiksi tällaisia tekstirivejä:

  • Tietokoneen komponenttien ja oheislaitteiden puolesta muille ohjelmistoille tarjotaan yhteneväinen …

  • Tietokoneen muistissa kukin lukuarvo vastaa tiettyä merkkiä …

Normaalisti grep tulostaa jokaisen rivin, josta tulee osumia, mutta voit myös pyytää grep-komentoa laskemaan rivien lukumäärän antamalla valitsimen -c. Esimerkiksi grep -c '^Tietokoneen' osa.txt laskee, kuinka moni rivi alkaa sanalla "Tietokoneen", kun taas grep -c 'Tietokoneen' osa.txt (huomaa, ei ^-merkkiä) laskee, kuinka monella rivillä esiintyy jossain kohtaa sana "Tietokoneen". Näytepalassa osa.txt luvut näyttäisivät olevan 20 ja 66. Kokeile samoja komentoja koko tiedostoon fiwiki.txt ja katso, mitä saat tulokseksi?

Vastaavasti merkki $ kohdistaa haun rivin loppuun. Voisimme etsiä tähän tapaan esimerkiksi rivejä, joiden lopussa on merkkijono "tietokone ="; nämä siis luultavasti olisivat artikkeleita, joiden nimi päättyy sanaan "tietokone":

grep 'tietokone =$' osa.txt

Tuloksena näyttäisi olevan kaksi riviä:

= Kannettava tietokone =
= Kvanttitietokone =

Kokeile tätä samaa hakua tietokone =$ koko tiedostolle fiwiki.txt; montako osumaa tulee?

Nyt osaisimme jo esimerkiksi hakea kaikki rivit, joiden alussa on "= ", tai kaikki rivit, joiden lopussa on " =". Riittäisikö tämä? Kokeillaan hiukan meidän näytetiedoston kanssa.

Kokeillaan molempia hakuja ja tallennetaan tulokset tiedostoihin:

grep '^= ' osa.txt > osa-otsikko-alku.txt
grep ' =$' osa.txt > osa-otsikko-loppu.txt

Jos hyvin käy, meillä pitäisi olla nyt molemmissa tiedostoissa jokainen otsikko eikä mitään ylimääräistä. Tiedostojen pitäisi olla identtisiä. Onkohan näin? Käy vilkaisemassa tiedostoja esim. less-komennolla!

Tiedosto osa-otsikko-alku.txt näyttää varsin lupaavalta, tiedoston alussa on tämän tyylistä tekstiä:

= Amsterdam =
= Aikido =
= Austronesialainen kielikunta =
= Algebrallinen luku =
= Alkuluku =
= Au =
= Alankomaat =
= Alkuaine =
= Luettelo alkuaineista =
= Avaruussukkula =

Sen sijaan tiedosto osa-otsikko-loppu.txt selvästi sisältää paljon roskaa, tähän tapaan:

= Amsterdam =
| muu_nimi =
| lippu_koko =
| vaakuna_koko =
| kilpi_koko =

Näköjään Wikipedian sivujen lähdekoodissa on paljon muitakin rivejä, joiden lopussa on "= "-merkki. Pelkästään rivin lopussa olevan " ="-merkkijonon hakeminen ei siis riitä.

Onkohan tiedostossa osa-otsikko-alku.txt myös jotain ylimääräistä? Nämä kaikki ovat tietysti rivejä, joiden alussa lukee "= ", mutta olisi hyvä tarkistaa, lukeeko niiden kaikkien lopussa " ="?

Nyt olisi hyödyllistä pystyä hakemaan rivejä, jotka eivät täsmää johonkin hahmoon. Tämä onnistuu grep-komennon valitsimella -v (eli pidemmin --invert-match). Etsitäänpä siis tiedostosta kaikki rivit, jotka eivät pääty " =":

grep -v ' =$' osa-otsikko-alku.txt

Näyttää löytyvän 16 riviä, joissa on ilmeisesti jotain matemaattista merkintää. Näistä pitäisi päästä eroon.

Piste ja tähti

Yritetään siis etsiä rivejä, joissa on sekä alussa "= " että lopussa " =". Tämän voi tehdä muutamaan eri tapaan. Voidaan edetä vaihe kerrallaan ja luoda ensin tiedosto, jossa on alkupään osumat ja sitten suodattaa sitä edelleen:

grep '^= ' osa.txt > osa-otsikko-alku.txt
grep ' =$' osa-otsikko-alku.txt > osa-otsikot.txt

Tai voimme jättää tilapäisen tiedoston luomatta ja putkittaa ensimmäisen grep-komennon tulosteen suoraan toiselle komennolle:

grep '^= ' osa.txt | grep ' =$' > osa-otsikot.txt

Tai voimme myös laatia yhden hahmon, joka täsmää juuri siihen, mitä haluamme. Säännöllisissä lausekkeissa . on erikoismerkki, joka täsmää mihin tahansa merkkiin; esimerkiksi tämä haku löytää merkkijonon "lapio" lisäksi myös merkkijonon "lppio" sanan "Tulppio" sisältä ja merkkijonon "lepio" sanan "Asklepios" sisältä (kokeile!):

grep --color 'l.pio' osa.txt

Toinen hyödyllinen erikoismerkki on *, joka tarkoittaa "toista edellistä 0 tai useampi kertaa". Siis vaikkapa lap*io osuisi tekstinpätkiin "laio", "lapio", "lappio", "lapppio" jne. Näitä voi yhdistääkin: lap.*io osuu samoihin tekstinpätkiin kuin säännölliset lausekkeet lapio, lap.io, lap..io, lap...io jne., joten tämä löytää sellaisia tekstinpätkiä kuin "lapinraunio" tai "koalapopulaatio" ja hyvin hyvin paljon muuta (huomaa, että . osuu myös välilyönteihin, välimerkkeihin ja kaikkeen mahdolliseen).

Siis tiivistettynä: .* tarkoittaa "ihan mitä tahansa, mikä määrä tahansa". Joten voimme löytää otsikot tällaisellakin haulla:

grep '^= .* =$' osa.txt > osa-otsikot.txt

Halutaan siis, että meillä on rivin alku (^), sitten tekstinpätkä "= ", sitten mitä tahansa (.*), sitten tekstinpätkä " =", ja tämän jälkeen rivin loppu ($).

Katsele nyt less-komennolla tiedostoa osa-otsikot.txt; alkaa näyttää hyvältä, nämä kaikki voisivat hyvinkin olla Wikipedian artikkelien otsikoita! Lasketaan kuinka monta riviä tiedostossa on, wc-komento sopii tähän, valitsimella -l se laskee rivit:

wc -l osa-otsikot.txt

Tulos näyttäisi olevan 9 149 riviä, hyvä. Tee nyt sinä vastaavat askeleet koko tiedostolle! Luo tiedoston fiwiki.txt pohjalta tiedosto fiwiki-otsikot.txt, johon on haettu kaikki rivit, joiden alussa on "= " ja lopussa " =".

Montako riviä on tiedosto fiwiki-otsikot.txt?

Palautusta lähetetään...