Unix-opas, luku 5 Muita toimintoja ja sovelluksia Unix-koneissa:

Työkaluajattelu

Yleistä työkaluajattelusta

Unixin yhteydessä puhutaan usein työkaluajattelusta. Tällä tarkoitetaan suurin piirtein sellaista lähestymistapaa, että komentoja ja ohjelmia tarkastellaan työkaluina (tools), joista kullakin hoidetaan yksi tehtävä; kun halutaan tehdä monimutkaisempi asia, ei tehdä sitä varten uutta ohjelmaa, vaan pyritään ensisijaisesti hoitamaan se olemassaolevia työkaluja yhdistelemällä.

Syötön ja tulostuksen uudelleenohjaus ja erityisesti putkitus ovat keskeisiä välineitä työkalujen käytön yhdistämisessä. Putkituksella voidaan ikäänkuin muodostaa liukuhihna, jonka läpi käsiteltävä tietoaineisto kulkee. Kussakin vaiheessa sitä muokkaa jokin työkalu, jota voidaan verrata liukuhihnan äärellä työskentelevään robottiin. Kukin robotti saa raaka-aineensa (syötteensä) liukuhihnan edelliseltä robotilta, ja tuotteet (tulosteet) menevät raaka-aineiksi seuraavalle robotille.

Varsin usein, mutta ei välttämättä, työkalu on filtteri. Unix-komentojen ja -ohjelmien joukossa ei ole mitään erityistä työkalujen luokkaa, vaan lähes kaikkia niistä voidaan käyttää työkaluina - toisia paremmin, toisia huonommin.

Esimerkkejä

Seuraava hyvin yksinkertainen esimerkki havainnollistanee työkaluajattelua. Oletetaan, että meillä on tekstitiedosto puhlu, joka sisältää puhelinluettelon. Tiedoston kukin rivi sisältää yhden henkilön tiedot siten, että tiedot ovat kiinteästi samoissa sarakkeissa seuraavaan tapaan:

Hiiri, Mikki        4040  555 5555
Ankka, Roope        4300  123 4567
Ankka, Aku          4301  333 3333
...
Tässä sarakkeet 1 - 20 sisältävät nimen ja sarakkeet 21 - 24 sisäisen puhelinnumeron, ja sarakkeesta 25 alkaa kotipuhelinnumero. Oletetaan, että haluamme tuottaa uuden tiedoston, joka sisältää tiedot ilman kotipuhelinnumeroa nimen mukaan aakkosjärjestyksessä. Voisimme tehdä jollain ohjelmointikielellä (esim. C tai Fortran) ohjelman tähän tarkoitukseen, mutta se olisi tarpeettoman työlästä, varsinkin jos tehtävä on pieni ja kertaluonteinen. Ilman ohjelmointitaitoakin tehtävä onnistuu Unixin työkaluilla näin:
colrm 25 < puhlu | sort > puhlu2
Tällöin tiedosto puhlu ensin annetaan syötteeksi colrm-ohjelmalle, jonka argumentilla käsketään sitä poistamaan joka rivin loppu 25. merkistä alkaen. Tulos putkitetaan sort-ohjelmalle, jonka käyttö yleisesti ottaen on aika hankalaa mutta tällaisessa yksinkertaisimmassa tapauksessa hyvin helppoa. Tulos ohjataan tiedostoon puhlu2, jonka sisällöksi näin muodostuu seuraava:
Ankka, Aku          4301
Ankka, Roope        4300
Hiiri, Mikki        4040
Putkituksella muodostetut tiedon käsittelyn "liukuhihnat" voivat olla varsin pitkiäkin; edellisessä esimerkissähän oli vain kaksi "robottia" colrm ja sort. Seuraavassa on esimerkki putkituksesta, jonka yksityiskohtiin emme paneudu mutta joka hyvin kuvastaa työkaluajattelua:
tar -cf foo.tar goo | compress | uuencode foo.tar.Z | mail joe
Tässä alkuperäinen tietoaineisto (hakemisto goo) annetaan syötteeksi tar-ohjelmalle, joka paketoi hakemiston sisällön yhdeksi tiedostoksi. Tulos menee syötteeksi compress-ohjelmalle, joka eräällä menetelmällä tiivistää tiedon pienempään tilaan. Sen tulos taas menee uuencode-ohjelmalle, joka muuntaa tiedoston sellaiseen esitysmuotoon, että se voidaan ongelmitta lähettää meilitse. Ja tämä muunnettu tulos lopuksi meneekin syötteeksi mail-ohjelmalle, joka on yksinkertainen mutta tällaisissa tapauksissa näppärä meiliohjelma.

Esimerkkejä työkaluohjelmista

Edellä on jo käsitelty lyhyesti muutamia työkaluohjelmia. Seuraavassa kuvataan muutaman muun yksinkertaisen työkaluohjelman peruskäyttöä. Tarkempia tietoja kunkin ohjelman tarjoamista mahdollisuuksista ja niiden käytöstä saa man-komennolla. Tässä käsiteltävät työkaluohjelmat ovat sellaisia, että niitä voidaan käyttää filttereinä.

grep-ohjelmalla voidaan poimia tietoaineistosta määrätyt ehdot täyttävät rivit. Yksinkertaisimmassa tapauksessa ehto on sellainen, että rivillä tulee esiintyä jokin määrätty merkkijono, joka annetaan ohjelmalle argumenttina. Esimerkiksi komento

grep Unix <foo

tulostaa (standarditulostusvirtaan) ne ja vain ne foo-tiedoston rivit, joilla esiintyy merkkijono Unix (juuri näin kirjoitettuna, mutta mahdollisesti sanan osana, esim. sanassa Unixissa). Esimerkki grepin käytöstä putkituksessa on seuraava, missä who-komennon tulostuksesta (joka voi olla varsin pitkä) poimitaan vain ne rivit, joilla esiintyy merkkijono dio:

epsilon ~ 51 % who | grep dio
dio         ttyb7       Feb 20 20:27         
dio         ttya5       Feb 20 08:30         
dio         ttyb5       Feb 20 13:58         
dio         ttybd       Feb 20 22:47         
epsilon ~ 52 %
Ohjelmalla grep on myös "serkut" fgrep, joka rajoittuneempi mutta usein tehokkaampi kuin grep, ja egrep, joka on monipuolisempi kuin grep.

diff-ohjelmalla voi vertailla tiedostoja ja tulostaa niiden erot (differences). Tiedostojen nimet annetaan argumentteina. Jos tiedostojen sisällöt ovat täsmälleen samat, diff ei tulosta mitään. Jos eroja on, niin ja diff tarkastelee ensimmäistä tiedostoa perustiedostona tulostaen tiedot siitä, miten toinen tiedosto poikkeaa siitä. Tulostuksen muoto lienee paras selostaa esimerkin avulla:

epsilon ~/tmp 52 % diff tlo.old tlo
19c19
< Yksi pysyvä ongelma on muutosten nopeus. Tänään suositeltava ohjelma on
---
> Yksi pysyvä ongelma on muutosten nopeus, sillä tänään suositeltava ohjelma on
82d81
<     POP-palvelu
119a119,120
> 
> 27.2.1996
epsilon ~/tmp 53 %
Tässä tapauksessa löytyi tiedostosta tlo kolme eroa tiedostoon tlo.old verrattuna:
  1. Otsikkotieto 19c19 kertoo muutoksesta (change) rivillä, tarkemmin sanoen, että ensimmäisen tiedoston 19. rivi poikkeaa toisen tiedoston vastaavasta rivistä. Kummatkin rivit näkyvät tulosteessa siten, että ensimmäisen edessä on <-merkki ja toisen edessä >-merkki.
  2. Otsikkotieto 82d81 kertoo, että ensimmäisestä tiedostosta on poistettu (delete) 82. rivi; kyseinen rivi näkyy tulosteessa.
  3. Otsikkotieto 119a119,120 kertoo, että ensimmäisen tiedoston 119. rivin perään on lisätty (add) kaksi riviä; rivit näkyvät tulosteessa.
On mahdollista muuttaa erojen tulostuksen ulkoasua valitsimilla. Erityisesti mainittakoon valitsin -c, joka aiheuttaa sen, että tulostus on pitempi siten, että eroavuuksien yhteyteen tulostuu tiedostojen sisältöä laajemmin; silloin ehkä näkee helpommin muutettujen tekstien asiayhteyden (context).

tr-ohjelma tekee tietoaineistolle yksinkertaisen merkkimuunnoksen eli -translaation, jossa annetut merkit korvataan toisilla. Esimerkiksi komento

tr '{|}[\]' 'äöåÄÖÅ'
kopioi standardisyöttövirran standarditulostusvirtaan siten, että merkki { korvautuu merkillä ä, merkki | merkillä ö jne. Jos tällainen muunnos halutaan tehdä tiedostolle, toimitaan seuraavaan tapaan (koska tr on filtteri):
tr '{|}[\]' 'äöåÄÖÅ' <muistio.asc >muistio.iso

Työkaluista pieniin kieliin ja ohjelmointiin

Kun halutaan tehdä asioita, joita ei voi suoraan hoitaa yhdellä komennolla tai valmiilla ohjelmalla, voidaan valita jokin seuraavista toimintatavoista:

Putkitusta käsiteltiin juuri edellä ja käännettäviä ohjelmia aiemmin. Komentotiedostoista tulee puhetta myöhemmin. Seuraavassa kuvaillaan hiukan tulkittavia kieliä.

Valinta edellä mainittujen vaihtoehtojen välillä riippuu monista asioista kuten tehtävän mutkikkuudesta ja siitä, mitä välineitä käyttäjä osaa käyttää. Usein tilanne on se, että tehtävä hoituisi nopeimmin jollain sellaisella välineellä, jota käyttäjä ei tunne. Tällöin on tietysti otettava huomioon, että uuden välineen opetteluun voi kulua paljonkin aikaa - ja toisaalta siitä voi olla paljon hyötyä myöhemmin.

Tehokkuusseikat kannattaa myös ottaa huomioon. Kertaluonteisiin hommiin sopii yleensä hyvin työkaluista tehty putki tai vaativammissa tapauksissa komentotiedosto tai pienen kielen käyttö. Jos taas on kehitettävä tapa tehdä homma, joka joudutaan tekemään usein ja joka kuluttaa paljon tietokoneen aikaa, voi ohjelmointikielen käyttö olla hyvin perusteltua. Tällöin on olennaista, että ohjelma käännetään konekoodiksi, jota tietokone suoraan toteuttaa; sen sijaan pieniä kieliä käytettäessä suoritus on tulkitsevaa, jolloin tietokoneajan kulutus voi olla monikymmenkertainen.

Pienet kielet muistuttavat monessa suhteessa varsinaisia ohjelmointikieliä - niissä on yleensä mm. ehto- ja toistorakenteita - mutta toteutus on tulkitseva. Pienellä kielellä kirjoitettu ohjelma voidaan joskus kirjoittaa suoraan komentoriville (ns. one-liner, yhden rivin mittainen ohjelma), mutta tavallisempaa on, että se kirjoitetaan tiedostoon. Seuraavassa kuvaillaan lyhyesti muutamia pieniä kieliä luonnehtien niiden yleisiä ominaisuuksia ja esittäen pikku esimerkkejä. Pienien kielien oppimisessa on oma vaivansa, joten kannattaa harkita, mihin niistä perehtyy, sen mukaan, miten ne tuntuvat sopivan omiin tarpeisiin.

Sed (stream editor) on Unixin alkuperäisestä rivieditorista (Ed) kehitetty ohjelma, joka on monessa suhteessa lähempänä työkaluohjelmaa kuin pientä kieltä - näiden käsitteiden välillä ei ole tarkkaa rajaa. Sedillä voidaan muokata tiedostoa esimerkiksi korvaamalla merkkijonoja toisilla. Samanlaisia asioita voidaan tehdä editoreillakin, mutta editorin käyttö on vuorovaikutteista, Sedin käyttö taas sellaista, jossa käyttäjä vain kuvaa halutun muunnoksen ja käskee Sedin tehdä sen. Tämän takia Sed sopii käytettäväksi työkaluajattelun mukaisessa putkituksessa. Seuraavassa esimerkissä tehdään muunnos, jossa tiedostossa olevat merkkijonot Atk ja atk korvataan merkkijonolla ATK:

sed -e 's/[Aa]tk/ATK/g' <ohje >ohje.uusi
Tässä esimerkissä valitsin -e kertoo, että sen perässä, suoraan komentorivillä, tulee Sedille annettava käsky; käskyt voitaisiin kirjoittaa myös tiedostoon. Käsky on s-käsky, joka määrää suorittamaan korvauksen (substitution), ja sen lopussa oleva g määrää tekemään korvauksen kaikkialle, yleisesti (generally). Rakenne [Aa] on alkeellinen esimerkki Sedin tunnistamista säännöllisistä lausekkeista (regular expressions): se tarkoittaa kumpaa tahansa merkeistä A ja a, joten rakenne [Aa]tk tarkoittaa siis merkkijonoja Atk ja atk.

Awk on monipuolisempi väline, johon liittyy oma pieni kielensä, joka muistuttaa C-ohjelmointikieltä. Awk-kielellä käsitellään tietoaineistoa riveittäin ja määritellään malleja (patterns) ja niitä vastaavia toimenpiteitä. Tämä esitetään Awk-ohjelmassa seuraavassa muodossa:

malli { toimenpide }

Täten voidaan erilaisille, siis eri malleja vastaaville, riveille määritellä erilaisia muunnoksia tai muita toimenpiteitä. Oletustoimenpiteenä on (mallia vastaavan) rivin kopiointi standarditulostusvirtaan. Tämän ansiosta voidaan asiat usein esittää hyvinkin tiiviisti; esimerkiksi

awk 'length > 72' tied
tulostaa standarditulostusvirtaan ne ja vain ne tied-tiedoston rivit, joiden pituus on yli 72 merkkiä. Tässä length > 72 kuvaa mallin, ja sitä vastaavat rivit tulostuvat, muut eivät; tätä mallia vartenhan ei ole erikseen määritelty toimenpidettä, joten Awk oletusarvoisesti suorittaa kopioinnin, ja ne rivit, joiden pituus on enintään 72 merkkiä, eivät vastaa mitään mallia (muita mallejahan ei tässä tapauksessa) ole, joten niiden osalta Awk ei tee yhtään mitään. Seuraavassa on hiukan pidempi esimerkki: tiedostossa isot on Awk-ohjelma
BEGIN      { sum = 0; print "Isot tiedostot:" }
$5 > 10000 { sum += $5; print }
END        { print "Isoissa tiedostoissa yhteensä " sum " tavua" }
ja nyt käyttäjä voi katsoa, mitä isoja (tässä tapauksessa: yli 10000 tavua pitkiä) tiedostoja hänellä on työhakemistossaan, seuraavasti:
epsilon ~/tmp 51 % ls -l | awk -f isot
Isot tiedostot:
-rw-r--r--   1 jkorpela staff      52876 Jan 24 14:45 btxdoc.dvi
-rw-r--r--   1 jkorpela staff      41569 Jan 24 14:44 btxdoc.tex
-rw-r--r--   1 jkorpela staff      10990 Jan 18 12:46 pctex
Isoissa tiedostoissa yhteensä 105435 tavua
epsilon ~/tmp 52 %
Tässä esimerkissä siis Awk-ohjelma on erillisessä tiedostossa, jonka nimi ilmoitetaan -f-valitsimella. Syötteenä Awk saa tässä komennon ls -l tulostuksen, ja se poimii siitä ne rivit, joissa 5. kenttä eli tiedoston koko tavuina on yli 10000. Poiminta hoidetaan mallilla $5 > 10000. Awk-ohjelmassa on myös "mallit" BEGIN ja END, joihin liitetyt toimenpiteet Awk suorittaa vastaavasti tietoaineiston käsittelyn alussa ja lopussa.

Perl on erittäin ilmaisuvoimainen kieli, jonka monet piirteet on otettu C-ohjelmointikielestä. Sitä ei oikein voi lukea "pieniin kieliin", mutta sitä voi käyttää niiden tapaan, hyvinkin pienten työkalujen tekemiseen. Perlin "filosofia" on aika erikoinen ja vaatii totuttelua; yleensäkin kielten opettelussa alkuvaihe on hankala mutta sen jälkeen lisäoppiminen on aika helppoa. Perlistä on olemassa erillinen ohje Getting started with Perl ja myös suomenkielistä aineistoa. Seuraavassa on vain pieni esimerkki Perlin tarjoamista mahdollisuuksista:

#!/usr/bin/perl
while(<*.for>) {
  $oldname = $_;
  s/\.for$/\.f/;
  rename $oldname, $_;
}
Tämä Perl-ohjelma muuttaa kaikkien työhakemistossa olevat .for-loppuiset tiedostonnimet vastaaviksi .f-loppuisiksi. Tällaista uudelleennimeämistä ei Unixissa voi tehdä millään suoralla tavalla, esim. mv-komennolla! Ohjelman ensimmäinen rivi kertoo, että kyseessä on Perl-ohjelma. Toinen rivi määrää ohjelman käymään läpi kaikki .for-loppuiset tiedostonnimet, ja tämän silmukan sisällä sitten korvataan nimen loppuosa .for merkkijonolla .f ja suoritetaan uudelleennimeäminen tiedosto kerrallaan Perl-käskyllä rename, joka olennaisesti vastaa Unixin mv-komentoa.