- CS-A0100
- 2. Wikipedia-projekti
- 2.12 Siivoamista: sed
Siivoamista: sed¶
Tähän asti olemme oppineet, että suomenkielisen Wikipedian yleisin sana näyttäisi olevan "ref" ja kärjessä on myös sen tyylisiä sanoja kuin "http", "www" ja "fi". Syy selviää äkkiä, kun hakee tiedostosta sanan "ref" esiintymiä, esimerkiksi tyyliin grep --color '\<ref\>'
. Wikipedian lähdekoodi on täynnä tämän kaltaisia lähdeviitteitä:
<ref name="kuinkajohdetaan">{{Verkkoviite | Osoite = https://dev.hel.fi/paatokset/media/att/1c/1c3497c4d905d089f74c946d08268dd468c63f20.pdf| Nimeke = Kuinka eurooppalaisia kaupunkeja johdetaan?| Tekijä = Backman, Katri| Tiedostomuoto = pdf| Julkaisupaikka = Helsinki| Julkaisija = Helsingin kaupunki, Tietokeskus| Viitattu = 17.5.2018}}</ref>
<ref>[http://www.mersenne.org/ http://www.mersenne.org]</ref>
<ref name=mj2>Majaranta, Leo, s. 12–13</ref>
<ref name="tulokset" />
Saisimmeko siivottua nämä kaikki pois sotkemasta? Tämä on taas hiukan huonosti määritelty tehtävä, kun Wikipedia on lopulta ihmisten käsin kirjoittamaa koodia, <ref>
-komentoja on käytetty aika sekalaisesti ja koodissa on kirjoitusvirheitäkin. Mutta yritetään siivoilla näistä silti suurin osa pois. Karkeasti ottaen haluaisimme siis poistaa tämän kaltaisia rimpsuja:
<ref>...</ref>
<ref name=...>...</ref>
<ref name=... />
Mutta ollaan huolellisia: jos tekstissä lukee vaikkapa
a <ref>b</ref> c <ref>d</ref> e
emme halua siivota pois koko pätkää <ref>b</ref> c <ref>d</ref>
vaikka se onkin periaatteessa muotoa <ref>...</ref>
.
Etsi-korvaa¶
Haluaisimme siis tehdä jonkinlaista automaattista etsi-korvaamista tekstitiedostolle. Tähän hyvä työkalu on sed
. Tämä on awk
:n tapaan hyvinkin monipuolinen väline, mutta käytännössä useimmat käyttävät vain yhtä sed
:n toimintoa:
sed 's#hahmo#korvaus#'
Tämä lukee syötettä, etsii jokaiselta hahmon ensimmäisen esiintymän, ja korvaa sen halutulla merkkijonolla. Alussa oleva komento s
kertoo, että olemme tekemässä juuri tätä etsi-korvaa-toimintoa (monia muitakin komentoja olisi). Erotinmerkin #
paikalla voi käyttää melkein mitä tahansa välimerkkiä, esimerkiksi tämä toimii myös (kunhan hahmossa ja korvauksessa ei esiinny pilkkuja):
sed 's,hahmo,korvaus,'
Tässä "hahmo" on säännöllinen lauseke samaan tapaan kuin grep
-komennossakin. Kokeillaan! Esimerkiksi
echo lapioijat | sed 's#i#XXX#'
tulostaa "lapXXXoijat": ensimmäinen hahmon "i" osuma siis korvattiin merkkijonolla "XXX". Jos haluat korvata kaikki osumat, lisää loppuun valitsin "g" (global):
sed 's#hahmo#korvaus#g'
Esimerkiksi tämä siis tuottaa tuloksen "lapXXXoXXXjat"; jokainen "i":n esiintymä on nyt korvattu (kokeile!):
echo lapioijat | sed 's#i#XXX#g'
Normaalisti merkkikoolla on väliä, mutta valitsin "I" (ignore case) muuttaa tämän:
sed 's#hahmo#korvaus#g'
Esimerkiksi tämä tulostaa "LAPIOIJAT":
echo LAPIOIJAT | sed 's#i#XXX#g'
Kun taas tämä tulostaakin "LAPXXXOXXXJAT":
echo LAPIOIJAT | sed 's#i#XXX#gI'
Turhien tägien siivoilua¶
Lähdetään nyt miettimään, miten nuo turhat <ref>
-tägit saisi siivottua pois. Luo ensin tiedosto ref.txt
, jossa on muutama perusesimerkki:
hyvä <ref>huono</ref> hyvä <ref>huono</ref> hyvä
Yksinkertaisimmillaan voisimme ensin poistaa merkkijonot <ref>
ja </ref>
, vaikkapa näin:
sed 's#<ref>##gI' ref.txt | sed 's#</ref>##gI'
Tässä siis ensin etsitään hahmoa <ref>
(kaikki osumat, merkkikoosta välittämättä) ja korvataan ne tyhjällä, ja lopputuloksessa tehdään sama hahmolle </ref>
. Lopputulos on tällainen:
hyvä huono hyvä huono hyvä
Haluaisimme kuitenkin poistaa myös kaiken roskan, mitä on tagien <ref>
ja </ref>
välissä. Ensimmäinen ajatus olisi varmaankin luoda hahmo <ref>.*</ref>
:
sed 's#<ref>.*</ref>##gI' ref.txt
Tässä ollaan kuitenkin liiankin ahneita ja jäljelle jää vain:
hyvä hyvä
Hahmo osui koko pätkään <ref>huono</ref> hyvä <ref>huono</ref>
. Emme siis oikeasti halua etsiä <ref>
+ ihan mitä tahansa + </ref>
, vaan hiukan rajallisemmin. Suhteellisen hyvä kompromissi voisi olla tällainen:
sed 's#<ref>[^<>]*</ref>##gI' ref.txt
Säännöllisissä lausekkeissa [^abc]
tarkoittaa "mikä tahansa merkki paitsi a, b ja c" ja tässä siis [^<>]
tarkoittaa "mikä tahansa merkki paitsi <
ja >
". Tuttuun tapaan *
tarkoittaa "toista kuinka monta kertaa tahansa". Eli etsitään siis <ref>
+ mikä tahansa tekstinpätkä, joka ei sisällä merkkejä <
ja >
+ </ref>
. Ollaan jo lähellä oikeaa, nyt tulostuu sitä mitä pitääkin:
hyvä hyvä hyvä
Tämä ei kuitenkaan vielä sellaisiin tapauksiin kuin <ref name=...>...</ref>
tai <ref name=... />
. Näihinkin voidaan kehittää samantyylisiä sääntöjä pienellä miettimisellä. Nämä näyttäisivät purevan aika hyvin useimpiin tilanteisiin:
<ref name *= *"[^"]*" *>[^<>]*</ref>
Tässä siis haetaan ensin
<ref name
, sen jälkeen mahdollisesti välilyöntejä mielivaltainen määrä (*
), tämän jälkeen=
, taas välilyöntejä mielivaltainen määrä, sitten lainausmerkki"
, sitten mitä tahansa muuta kuin lainausmekkejä ([^"]*
), toinen lainausmerkki"
, taas mahdollisia välilyöntejä, sitten mitä tahansa muuta kuin merkkejä<
ja>
([^<>]*
) ja lopuksi</ref>
.Tämä siis osuu sellaisiin tekstinpätkiin kuin
<ref name="foo">bar</ref>
tai<ref name = "foo" >bar</ref>
.
<ref name *= *"[^"]*" */>
Vastaavaan tapaan tämä osuu sellaisiin tekstinpätkiin kuin
<ref name="foo"/>
tai<ref name = "foo" />
.
<ref name *= *[^"<>]* *>[^<>]*</ref>
Tämä taas osuu esimerkiksi sellaiseen tekstinpätkään kuin
<ref name=foo>bar</ref>
, siis ilman lainausmerkkejä.
<ref name *= *[^"<>]* */>
Vastaavaan tapaan tämä osuu sellaisiin tekstinpätkiin kuin
<ref name=foo/>
.
Siivousskripti¶
Kootaan nämä kaikki yhdeksi skriptiksi, niin on helpompi kokeilla! Luo tiedosto siivoa.sh
, ja kirjoittele siihen nämä kaikki säännöt yhdeksi putkeksi:
#!/bin/bash
sed 's#<ref>[^<>]*</ref>##gI' |
sed 's#<ref name *= *"[^"]*" *>[^<>]*</ref>##gI' |
sed 's#<ref name *= *"[^"]*" */>##gI' |
sed 's#<ref name *= *[^"<>]* *>[^<>]*</ref>##gI' |
sed 's#<ref name *= *[^"<>]* */>##gI'
Anna taas tiedostolle ajo-oikeudet: chmod +x siivoa.sh
. Kokeillaan ensin perustapausta, että tämä edelleen varmasti toimii:
./siivoa.sh < ref.txt
Tulosteena pitäisi olla tuttuun tapaan "hyvä hyvä hyvä". Yritetään nyt suodattaa näytepala tämän skriptin avulla:
./siivoa.sh < osa.txt > osa-siivottu.txt
Tutkitaan, auttoiko!
Näyttäisi siis, että mennään hyvään suuntaan! Tutkitaan, miten tämä korjaus vaikuttaa sanamäärätilastoihin. Lasketaan ensin näytepalasta sanamäärät:
./sanat.sh < osa-siivottu.txt > osa-siivottu-sanat.txt
Katso tiedostoa osa-siivottu-sanat.txt
. Nyt ainakin kaksi yleisintä sanaa näyttäisi olevan järkeviä suomen sanoja: "ja" ja "on". Jäljellä olisi vielä paljon siivottavaa, esimerkiksi wikitekstissä esiintyvät tägit <br>
(rivinvaihto) ja
(katkeamaton välilyönti) sotkevat tilastoja, mutta jätetään näiden siivoilu myöhemmäksi oman harrastuneisuuden varaan.
Koko aineiston siivoaminen¶
Kokeillaan nyt tätä koko aineistoon! Jos levytilasta ei ole pulaa, voit edetä kahdessa vaiheessa:
./siivoa.sh < fiwiki.txt > fiwiki-siivottu.txt
./sanat.sh < fiwiki-siivottu.txt > fiwiki-sanat-siivottu.txt
Tai vaihtoehtoisesti voit koota kaiken yhteen putkeen, jolloin vältetään muutaman gigatavun kokoisen tilapäistiedoston luominen:
./siivoa.sh < fiwiki.txt | ./sanat.sh > fiwiki-sanat-siivottu.txt
Taas voi olla hyvä hetki käydä kahvilla tai tehdä vaikka lumityöt…