Perlin perusteita
(ilman kyyneleitä)

Tämä on kädestä pitäen -johdatus Perl-ohjelmointikieleen. Pääpaino on kielen yksinkertaisimmilla rakenteilla, ymmärrettävällä ohjelmointityylillä (lyhyyteen pyrkimättä) ja kielen soveltamisella pienehköjen, usein kertakäyttöisten työkalujen tekemiseen.

Tässä oppaassa on yritetty olla edellyttämättä aiempaa kokemusta ohjelmoinnista. Tässä ei edes verrata Perliä muihin kieliin, vaan mahdolliset yhtäläisyydet (ja erot) jätetään muita kieliä tuntevien lukijoiden itsensä oivallettaviksi. Jos tahti on sinulle liian hidas ja osaat englantia, harkitse siirtymistä lukemaan tiiviimpää tutoriaalia Perl Lessons.

Lukijan oletetaan kuitenkin tietävän jotain tietokoneista, ainakin osaavan käyttää jotakin tekstieditoria sekä tietävän, miten komentorivikomentoja annetaan siinä järjestelmässä, jota hän käyttää. Jos olet esimerkiksi Windows-käyttäjä, sinun siis pitäisi tietää, miten kirjoitetaan tekstitiedosto esim. NotePad- eli Muistio-ohjelmalla ja miten avataan komentoikkuna ("DOS-ikkuna"). Jos taas olet Unix-käyttäjä, niin luultavasti et ole voinut olla oppimatta tässä tarvittavia vastaavia asioita.

Johdatus alkeiden perusteisiin

Tässä ensimmäinen Perl-esimerkkiohjelmamme:

while(<>) {
   print; }

Tämä ohjelma tekee aika yksinkertaisen asian: se lukee tekstirivejä ja tulostaa ne. Emme vielä paneudu ohjelman sisäiseen rakenteeseen, vaan otamme sen tässä vain yhtenä Perlille tyypillisenä ilmaisuna, idiomina. Vihjaamme kuitenkin osien merkitykseen kertomalla, miten eri osat voidaan lukea:

while niin kauan kuin...
(<>) ...rivin lukeminen onnistuu (rivit eivät ole loppuneet)...
{ ... suorita seuraava:...
print; ... tulosta (juuri lukemasi rivi)...
} (loppu)

Kummallisin tässä kai on rakenne (<>), mutta se on toisaalta rakenne, joka tekee tällaisten ohjelmien kirjoittamisen Perlillä huomattavan näppäräksi verrattuna useimpiin muihin kieliin. Ja silläkin on oma selityksensä.

Ohjelma on erikoistapaus seuraavanlaisesta yleisestä muodosta, while-toistorakenteesta:
while(ehto) { toiminto }
Välimerkit ovat tässä olennaisia. Tavalliset sulkeet () tarvitaan aina ehdon ympärille. Ja aaltosulkeet {} rajaavat sen, mikä kaikki ohjelmassa on kyseisen toiston piirissä. Ohjelmassahan saattaa myöhemmin olla muita toimintoja, joita ei pidä toistettaman; voisimme esimerkiksi lisätä ohjelman loppuun lauseen
print 'Loppu';
jota ei tietenkään pidä toistaa! Ja homma hoituu, kunhan se kirjoitetaan edellä mainitun loppuaaltosulkeen } jälkeen. Huomaa, että ohjelman jakeminen eri riveille ja sisentäminen ei ole sen toiminnan kannalta merkitsevää vaan tehdään, jotta ihmisen on mukavampi lukea ohjelmaa ja hahmottaa sen rakenne.

Lisätään tekstin muokkausta

Ohjelman toiminta ei kuulosta kovinkaan hyödylliseltä - tai pikemminkin tulee mieleen, että sellainenhan voidaan tehdä jollakin valmiilla komennolla tai muulla toimenpiteellä ilman mitään ohjelmointia. Totta. Mutta kun tällainen ohjelman runko on valmiina, niin siihen voi sitten lisätä kaikenlaista rivien muokkausta ennen tulostusta. Esimerkiksi näin:

while(<>) {
   s/!+/!/g;
   print; }

Tässä on ohjelmaan lisätty ihmeellisen loitsun näköinen
s/!+/!/g;
jonka vaikutus on se, että se korvaa jokaisen tekstissä olevan peräkkäisten huutomerkkien jonon yhdellä huutomerkillä. Siis "Hei!!! Auttakaa mua!!!!" muuntuu siistimpään muotoon "Hei! Auttakaa mua!".

Loitsu on korvauskäsky eli s-käsky, ja se on asiaa tuntemattomalle hämärän näköinen, koska se on niin lyhyt, ja se on lyhyt, jotta se olisi nopea kirjoittaa. Tällaiset käskyt nimittäin ovat Perlille hyvin ominaisia ja paljon käytettyjä. Katsotaanpa, mitä esimerkkikäsky oikein sisältää:

Käsky on siis seuraavaa yleistä muotoa:
s/korvattava/korvaava/valitsimia;

Mutta mitä kummallinen !+ tarkoittaa? Eikös ollut kyse huutomerkkijonojen korvaamisesta? Aivan. Tällaisissa yhteyksissä, siis korvauskäskyssä korvattavan merkkijonon paikalla, joillakin merkeillä on erikoismerkitys. Erityisesti + ei tarkoitakaan itseään, vaan se tarkoittaa edellä olevan merkin mahdollista toistumista. Siis !+ tarkoittaa mitä tahansa merkkijonoista !, !!, !!!, !!!! jne.

Tarkkaavainen lukija tulee ehkä ajatelleeksi, että edellä käytetty käsky aiheuttaa turhaakin työtä, nimittäin myös yhden huutomerkin korvaamisen yhdellä huutomerkillä. Totta. Se ei kuitenkaan ole vakavaa, ainakaan tässä. Se voitaisiin välttää monella tavalla, yksinkertaisimmin kai kirjoittamalla s/!!+/!/g;.

Merkit, joilla on erikoismerkitys korvauskäskyissä, ovat seuraavat:

/+?.*^$()[]{}|\

Niiden käyttöön tutustutaan tarkemmin jäljempänä. Mutta joku kuitenkin haluaa tietää, mitä tehdä sitten, jos haluaakin vaikkapa korvata plusmerkit jollakin, esimerkiksi merkkijonolla "ynnä". Ja vastaus on, että merkin erikoismerkityksen voi poistaa kirjoittamalla sen eteen kenoviivan \, joka siis toimii tavallaan lainausmerkkinä:
s/\+/ynnä/g;

Vaikeaa? No, korvauskäsky voisi toki olla paljon yksinkertaisempikin, sellainen, jossa ei ole mitään erikoismerkkejä:
s/Jukka/Yucca/g;
Tämä korvaisi merkkijonon "Jukka" jokaisen esiintymän merkkijonolla "Yucca". Huomaa, että esimerkiksi merkkijonot "jukka" ja "Jukan" jäisivät muuttumatta ja että "Jukkaa" muuttuisi muotoon "Yuccaa", mutta vain siksi, että sen osana on "Jukka". Korvauskäsky käsittelee merkkien jonoja kiinnittämättä mitään huomiota merkkien mahdolliseen merkitykseen tai edes siihen, muodostavatko ne jotain sanan näköistä.

Kokeilemaan!

Nyt on aika kokeilla rauhassa. Kirjoitapa aluksi pieni tekstitiedosto, jossa on esimerkkiaineistoa edellä kuvatuilla pikku ohjelmilla käsiteltäväksi. Vaikkapa seuraavansisältöinen teksti, vaikkapa tiedostoon nimeltä ekakoe.txt:

Hei, minä olen esimerkkitiedosto!!! Tää on kivaa!!
Jukka on hauska nimi. Moni Jukkakin on hauska!!
Siinä kaikki!!

Kirjoita sitten toiseen tiedostoon, nimeltään vaikkapa ekakoe.pl, edellä esitetty huutomerkkienkorvausohjelma:

while(<>) {
   s/!+/!/g;
   print; }

Sitten kokeilemaan. Ai niin, siihen tarvitaan Perl-kielen toteutus, Perl-tulkki (interpreter) eli ohjelma, joka lukee Perl-ohjelmia ja suorittaa ne. Tavallisesti Perl-tulkkia käytetään komentorivitilassa eli järjestelmän komentotasolla annetaan esim. komento
perl ekakoe.pl <ekakoe.txt
jolloin kuvaruudulle pitäisi tulostua esimerkkiteksti sellaisena, kuin se on Perl-ohjelmamme tekemän muokkauksen jäljiltä.

Jos käytät Windowsia, komentorivitila tarkoittaa ns. DOS-tilaa (Command Prompt, "DOS-tila"), johon päästään esim. Start- eli Käynnistä-valikon kautta (kohdasta Programs, Ohjelmat). Sitten vain siirryt (lähinnä CD-käskyillä) siihen hakemistoon, jossa Perl-ohjelmasi on, ja annat edellä kuvatun tapaisen komennon. Mutta on hyvin mahdollista, että joudut hankkimaan ja asentamaan Perl-tulkin, ennen kuin voit harjoitella Perl-ohjelmilla. Näin nimittäin on, jos et ole itse asentanut Perl-tulkkia koneeseesi eikä kukaan muukaan ole sitä tehnyt; itse Windowsin mukana ei tule Perl-tulkkia. Toisaalta kyse ei ole niin isosta asiasta kuin voisi luulla, ja se tarvitsee tehdä vain kerran. Tietoja siitä, mistä saa Perl-tulkkeja eri järjestelmiin, löytyy osoitteen
http://www.perl.com/pub/language/info/software.html
kautta. Erinomainen vaihtoehto Windowsille (ns. 32-bittisille Windowseille: Windows 95, Windows 98, Windows NT, Windows 2000) on ActivePerl, osoite:
http://www.activestate.com/Products/ActivePerl/
Valitettavasti kyseessä on isohko ohjelma, joten sen imuroimiseen Internetistä menee aikaa varsinkin modemiyhteydellä; kannattaa ehkä kysellä lähiympäristöstä, olisiko jollakulla tutulla ActivePerl-jakelupakettia CD-romilla. (ActivePerliä saa kopioida ja jakaa varsin vapaasti, ks. lisenssiehtoja.) Itse asennus on aika helppo; jos olet joskus asentanut Windowsiin ohjelmia, homman pitäisi olla helppo nakki. Jos et, on aika opetella.

Jos taas käytät jonkinlaista Unix-konetta, siinä luultavasti on Perl-tulkki valmiina, yleensä nimellä perl. Voit kokeilla antaa (Unixin komentotasolla, "shellissä") komennon
perl ekakoe.pl <ekakoe.txt
(Komentotasolle pääset Unixissa poistumalla siitä ohjelmasta, jossa olet, esim. editorista, jollakin ohjelmakohtaisella tavalla.) Jos tämä ei toimi, ota yhteys koneen ylläpitoon ja tiedustele, miten saat Perl-tulkin käyttöösi. Se saattaa olla asennettuna niin, että joudut tekemään jonkin erityisen toimenpiteen voidaksesi käyttää sitä.

No, miten kävi? Toivottavasti jotenkin näin (esimerkki on tässä ajettu eräässä Unix-koneessa):

gamma perl 71 % perl ekakoe.pl <ekakoe.txt
Hei, minä olen esimerkkitiedosto! Tää on kivaa!
Jukka on hauska nimi. Moni Jukkakin on hauska!
Siinä kaikki!
gamma perl 72 %

Kun haluat kokeilla Perl-ohjelmia, jotka tulostavat paljon tekstiä, kannattaa ehkä ohjata tulostus tiedostoon. Arvaat ehkä, kuinka. Kuten rakenne <tiedostonnimi edellä mainitulla komentorivillä ohjaa Perl-ohjelman lukemaan lähtöaineiston tiedostosta tiedostonnimi, voimme vastaavalla rakenteella mutta >-merkkiä käyttäen ohjata tulostuksen haluttuun tiedostoon. Esimerkki:
perl ekakoe.pl <ekakoe.txt >ekakoe.tul

Tämä on Perl-tulkille (nimeltään perl) annettu käsky lukea tiedostossa ekakoe.pl oleva Perl-ohjelma ja suorittaa se, tarkemmin sanoen siten, että lähtöaineisto tulee lukea tiedostosta ekakoe.txt ja tulokset kirjoittaa tiedostoon ekakoe.tul.

Perl-ohjelmassa ei siis tarvitse lyödä lukkoon, minkänimisestä tiedostosta se lukee lähtöaineiston ja minkänimiseen tiedostoon se kirjoittaa tuloksensa. Myöhemmin opimme, että niin toki voidaan tehdä - ja usein kannattaakin tehdä. Mutta toistaiseksi oletamme, että tiedostot ilmoitetaan itse Perl-ohjelman ulkopuolella edellä kuvatulla tavalla. Itse asiassa voimme kyllä jättää ne ilmoittamattakin eli käynnistää ohjelman yksinkertaisesti tyyliin perl ekakoe.pl, jolloin ohjelmalle pitää antaa lähtöaineisto suoraan näppäimistöltä näpyttelemällä ja se kirjoittaa tulokset suoraan kuvaruutuun.

Nyt voisit jo harjoitella yksinkertaisia tekstiaineiston muunnoksia itse tekemilläsi Perl-ohjelmilla. Huomaa, että yhden s-käskyn jälkeen, ennen print-käskyä, voi olla lisää s-käskyjä, jotka muokkaavat käsittelyssä olevaa riviä lisää. Ehkäpä haluaisit kokeilla voimiasi seuraavaan tehtävään. Sellaista oikeasti tarvitaan eräissä yhteyksissä Web-sivujen teossa; emme kuitenkaan nyt puutu siihen, miksi homma tehdään, koska se ei ole olennaista tälle harjoittelulle, vaan annamme vain teknisen kuvauksen siitä, mitä pitää tehdä:

Vakiomerkkijono voidaan tulostaa käskyllä, joka on muotoa
print 'merkkijono';
(Huomaa, että merkkijonon ympärillä on heittomerkit, ei aksenttimerkkejä. Tavallisessa suomalaisessa näppäimistössä heittomerkin saa näppäimestä, joka on ä-näppäimen oikealla puolella.) Ja koska kyseiset tulostukset pitää tehdä vain kerran eikä jokaista syöttötietoriviä kohti, niiden paikka on koko while-silmukan ulkopuolella.

Kokeilepa ratkaista tehtävä itse, ja testaa ohjelmaasi vaikkapa lähtöaineistolla

Hauska <em>Testi</em>.
Oikein &quot;hauska&quot;.
Loppu & slut.
ja vasta sitten vertaa ratkaisuasi malliratkaisuun ja sen tulosteeseen

Hahmontunnistus ja ehdollisuus

Seuraavaksi siirrymme oikeastaan yksinkertaisempiin asioihin. Edellä käsitelty s-käsky kuten s/Jukka/Yucca/ tekee kaksi asiaa:

  1. sen tunnistaminen, esiintyykö datassa merkkijono, joka vastaa käskyssä olevaa hahmoa (pattern), esimerkissämme kiinteä merkkijono Jukka
  2. mahdollisesti löytyneen merkkijonon korvaaminen toisella merkkijonolla.

Tunnistuskäsky eli m-käsky tekee näistä vain ensimmäisen. Koska korvaavaa merkkijono ei ole, rakenne on yksinkertaisesti
m/merkkijono/valitsimia
mistä valitsimet voivat puuttuakin. Esimerkiksi
m/Jukka/
yksinkertaisesti tunnistaa, esiintyykö käsittelyssä olevassa merkkijonossa jossakin kohtaa merkkijono Jukka (juuri tässä muodossa). Käskyn nimi johtuu englannin sanasta match.

Tunnistamisesta ei tietenkään ole hyötyä, ellei tietoa sen onnistumisesta käytetä mihinkään. Niinpä tunnistuskäskyä käytetäänkin useimmiten ehtolausekkeena, jonka arvo eli se, onnistuuko tunnistus, vaikuttaa siihen, mitä ohjelma seuraavaksi tekee. Tarkkaavainen lukija on ehkä huomannut, että edellä ei ollut puolipistettä käskyn lopussa. Itse asiassa syynä on juuri se, että m-käskyä käytetäänkin tavallisesti lausekkeena, jolloin sen perään ei kuulu puolipistettä.

Erittäin tavallinen rakenne, jossa käytetään ehtolauseketta, on if-lause, joka yksinkertaisimmillaan on muotoa
if(ehtolauseke) {lausejono}
ja se aiheuttaa ehtolausekkeen arvon laskemisen, jonka jälkeen Perl-tulkki suorittaa lausejonon, jos tulos oli "tosi", muussa tapauksessa ei seuraa mitään (vaan ohjelman suoritus jatkuu seuraavasta lauseesta).

Seuraava esimerkkiohjelma on muokattu alussa esittämästämme yksinkertaisesta kopiointiohjelmasta tekemällä käskyn print; suoritus ehdolliseksi:

while(<>) {
   if(m/Jukka/) {
      print; } }

Muistutamme, että sisennykset eivät vaikuta ohjelman merkitykseen vaan ratkaisevaa on aaltosulkeiden käyttö. Tämäntapainen sisentely kuitenkin auttaa ohjelmaa lukevaa ihmistä hahmottamaan sen rakenteen - jos nimittäin sisennykset on tehty rakennetta vastaavalla tavalla!

Tämä ohjelma lukee lähtöaineistonsa rivi kerrallaan ja tulostaa ne rivit, joilla esiintyy jossakin kohdassa merkkijono Jukka. Tämäntapainen ohjelma voi siis toimia "suodattimena", joka poimii tiedostosta vain jonkin ehdon täyttävät rivit. Ehto voi olla hyvinkin monimutkainen, tai hyvin yksinkertainen kuten tässä.

Jos haluaisimme, että ohjelma tekemässään tunnistuksessa jättää isojen ja pienten kirjainten eron huomiotta (engl. case-insensitive match) eli esimerkkitapauksessamme tunnistaa myös rivit, joilla esiintyy vaikkapa JUKKA tai jukka tai juKkA, niin kirjoittaisimme vain m-käskyn loppuun kirjaimen i,

while(<>) {
   if(m/Jukka/i) {
      print; } }

Vastaavasti voisimme kirjoittaa s-käskyn s/Jukka/Yucca/i, mutta silloin i:n vaikutus kohdistuisi vain tunnistukseen, ei korvaamiseen. Esim. JUKKA ei korvautuisi merkkijonolla YUCCA vaan kiinteästi merkkijonolla Yucca.

(Jatkuu ehkä joskus. Seuraavaksi: säännölliset lausekkeet.)


Viimeisimmän päivityksen ajankohta: 2000-08-08

Jukka Korpela