Siirryt vähitellen ohjelmoinnin puolelle shelliskriptauksen kautta. Nyt on paras aika oppia hyvän ohjelmoijan tapoja. Koska tämä kirja on vain johdanto komentoriviin, käsittelemme muutamia tärkeitä vinkkejä, jotka pyörivät ylläpidettävyyden ympärillä.
Kun ohjelmoijat puhuvat ylläpidettävyydestä, he puhuvat helppoudesta, jolla ohjelmaa voidaan muutella, olipa sitten kyse virheiden korjauksesta, uusien toimintojen lisäämisestä, tai toimivuuden parantamisesta. Heikosti huollettavat ohjelmat on helppo huomata: niistä puuttuu rakenne, joten toiminnallisuus on levinnyt ympäriinsä. Kun painat tästä, se sekoaakin tuolta, mikä on todellinen painajainen. Yleisesti ottaen niitä on todella vaikea lukea. Ajattele vaikkapa tätä:
#!/bin/sh identify `find ~/Kuvat/Loma/2008 -name \*.jpg` | cut -d ' ' -f 3 | sort | uniq -c
Käytä suosikkieditoriasi tallentaaksesi tämän tiedoston nimellä foo, sitten:
$ chmod +x foo $ ./foo 11 2304x3072 12 3072x2304
Tämä pieni hirviö etsii tietyn hakemiston tiedostot, jotka loppuvat päätteeseen ".jpg", ajaa komennon identify
niille kaikille, ja raportoi sellaista tietoa, joka on jonkun mielestä ollut jossain vaiheessa erittäin hyödyllistä. Jos ohjelmoija olisi vain lisännyt jotain vihjeitä ohjelman suorittamista toimenpiteistä...
Ensinnäkin huomaat, että esimerkissämme heikosti ylläpidettävä ohjelma on yksi pitkä rivi. Näin ei tarvitse olla. Entäpä, jos ohjelma näyttäisikin tältä:
#!/bin/sh identify `find ~/Kuvat/Loma/2008 -name \*.jpg` | cut -d ' ' -f 3 | sort | uniq -c
On hieman helpompaa havaita missä kukin komento alkaa ja loppuu. Se on edelleenkin sama kokoelma putkitettuja ohjelmia, mutta ne on esitetty eri tavalla. Voit katkaista pitkät rivit putkien kohdalta, mutta niiden toiminnallisuus on silti sama.
Voit myös jakaa yhden komennon useampaan riviin käyttämällä \ -merkkiä rivin lopussa liittämään sen seuraavaan riviin:
#!/bin/sh echo Tämä \ on \ oikeasti \ yksi \ pitkä \ komento.
Toinen asia, jonka voit havaita, on skriptin nimi "foo". Tämä on lyhyt ja kätevä, mutta se ei tarjoa mitään vinkkiä ohjelman käyttötarkoitukseen. Entäpä tämä:
$ mv foo listaa_kuvien_koot
Nyt nimi auttaa käyttäjää ymmärtämään mitä skripti tekee. Paljon parempi, vai mitä?
Eräs ongelma ohjelmassa on sen tarkemerkkien (`) käyttö. Se toki toimii, mutta siinä on myös ongelmia. Ehkäpä suurin ongelma on se, jota ei ole helppo huomata: muista, että tarkemerkit laittavat sisältämänsä komennon ulostulon siihen kohtaan ohjelmassa, jossa ne ovat. Joissain ohjelmissa on rajoitettu komentorivin pituus. Tässä tapauksessa määritellyssä hakemistossa voi olla paljon kuvia, jolloin komentorivistä voi tulla epätavallisen pitkä. Tämä aiheuttaa omituisen virheviestin, kun ajat ohjelman. On monia tapoja korjata tämä ongelma, mutta tässä voimme kokeilla seuraavaa:
#!/bin/sh find ~/Kuvat/Loma/2008 -name \*.jpg | while read image ; do identify $image ; done | cut -d ' ' -f 3 | sort | uniq -c
Nyt find
toimii samoin kuin ennen, mutta sen ulostulo, tiedostonimien lista, putkitetaan while-silmukkaan. Tämän silmukan ehto on read image
. read
on funktio, joka lukee rivin kerrallaan, jakaa sen sisääntulon kenttiin ja laittaa jokaisen kentän muuttujaan, joka on tässä tapauksessa image. Nyt identify
toimii kuva kerrallaan.
Huomaa, kuinka muuttujan lisääminen tekee ohjelmasta helpommin luettavan: se sanoo kirjaimellisesti, että tahdot identifioida kuvan. Huomaa lisäksi, kuinka vaikutus tuleviin ohjelmoijiin ei olisi ollut sama, jos muuttujan nimi olisi ollut vaikkapa ovi tai cdrom. Nimet ovat tärkeitä!
Mutta tässä ohjelmassa on vieläkin jotain häiritsevää: hakemiston nimi hohtaa kuin kipeä peukalo. Mitä jos muutataisimme ohjelman tällaiseksi:
#!/bin/sh ALOITUS_HAKEMISTO=~/Kuvat/Loma/2008 find $ALOITUS_HAKEMISTO-name \*.jpg | while read image ; do identify $image ; done | cut -d ' ' -f 3 | sort | uniq -c
Tämä on hieman parempi: nyt voit muokata skriptiäsi ja muuttaa hakemistoa joka kerta, kun tahdot käsitellä jonkun toisen.
Loppu ei tuntunut aivan oikealta. Kuitenkaan et muuta komentoa ls
aina, kun tahdot listata eri hakemiston sisällön, ethän? Tehdäänpä ohjelmastamme yhtä joustava:
#!/bin/sh ALOITUS_HAKEMISTO=$1 find $ALOITUS_HAKEMISTO -name \*.jpg | while read image ; do identify $image ; done | cut -d ' ' -f 3 | sort | uniq -c
Muuttuja $1 on ensimmäinen parametri, jonka annat skriptillesi ($0 on ajamasi skriptin nimi). Nyt voit kutsua skriptiäsi näin:
$ ./listaa_kuvien_koot ~/Kuvat/Loma/2008
Tai voit listata vuoden 2007 kuvat, jos tahdot:
$ ./listaa_kuvien_koot ~/Kuvat/Loma/2007
Ajattele, mitä tapahtuu, jos ajat skriptin näin:
$ ./listaa_kuvien_koot
Ehkäpä tahdot tämän, mutta ehkäpä et. Tapahtuu niin, että $1 on tyhjä, joten $ALOITUS_HAKEMISTO on myös tyhjä ja ensimmäinen etsittävä parametri on myös tyhjä. Tämä merkitsee, että find etsii nykyisen työhakemistosi. Tahdot ehkä tehdä tästä käytöksestä näkyvää:
#!/bin/sh if test -n "$1" ; then ALOITUS_HAKEMISTO=$1 else LOPETUS_HAKEMISTO=. fi find $ALOITUS_HAKEMISTO -name \*.jpg | while read image ; do identify $image ; done | cut -d ' ' -f 3 | sort | uniq -c
Ohjelma toimii täsmälleen samoin kuin ennenkin, erona on vain se, että kun katsot ohjelmaa seuraavan kerran kuukauden päästä, et joudu arvailemaan, miksi se antaa tuloksia, vaikka et anna sille hakemistoa parametrinä.
Entäpä jos annat skriptille parametrin, mutta parametri ei ole hakemisto, tai sitä ei edes ole olemassa? Kokeile.
Ei kovinkaan kaunista?
Mitäs jos teemme näin:
#!/bin/sh if test -n "$1" ; then ALOITUS_HAKEMISTO=$1 else ALOITUS_HAKEMISTO=. fi if ! test -d $ALOITUS_HAKEMISTO ; then exit fi find $ALOITUS_HAKEMISTO -name \*.jpg | while read image ; do identify $image ; done | cut -d ' ' -f 3 | sort | uniq -c
Toimii paremmin. Nyt skripti ei edes yritä toimia, jos sen saama parametri ei ole hakemisto. Se ei ole kovin kohteliasta, se poistuu hiljaisesti ilman aavistustakaan siitä, mikä meni mynkään.
Se korjataan helposti:
#!/bin/sh if test -n "$1" ; then ALOITUS_HAKEMISTO=$1 else ALOITUS_HAKEMISTO=. fi if ! test -d $ALOITUS_HAKEMISTO; then echo \"$ALOITUS_HAKEMISTO\" ei ole hakemisto tai sitä ei ole olemassa. Loppu." exit fi find $ALOITUS_HAKEMISTO -name \*.jpg | while read image ; do identify $image ; done | cut -d ' ' -f 3 | sort | uniq -c
Ohjelma tuottaa nyt virheviestin, jos et anna sille parametrinä olemassaolevaa hakemistoa, ja poistuu ilman jatkotoimenpiteitä. Olisi mukavaa, jos antaisit muiden ohjelmien, jotka ehkä jatkossa kutsuvat ohjelmaasi, tietää että tuloksena oli virheviesti. Ohjelman olisi siis hyvä poistua virhekoodilla. Tähän malliin:
#!/bin/sh if test -n "$1" ; then ALOITUS_HAKEMISTO=$1 else ALOITUS_HAKEMISTO=. fi if ! test -d $ALOITUS_HAKEMISTO; then echo \"$ALOITUS_HAKEMISTO\" ei ole hakemisto tai sitä ei ole olemassa. Loppu. exit 1 fi find $ALOITUS_HAKEMISTO -name \*.jpg | while read image ; do identify $image ; done | cut -d ' ' -f 3 | sort | uniq -c
Nyt, jos kohdataan virheviesti, skriptisi poistumiskoodi on 1. Jos ohjelma poistuu normaalisti, poistumiskoodi on 0.
Mikä tahansa merkkiä # samalla rivillä seuraava jätetään huomiotta, joten voit kommentoida skriptisi toimintaa. Esimerkiksi:
#!/bin/sh # Tämä skripti raportoi kaikkien JPEG-tiedostojen koot nykyisessä hakemistossa # tai parametrinä annetussa hakemistossa, sekä jokaisen koon kuvien lukumäärän. if test -n "$1" ; then ALOITUS_HAKEMISTO =$1 else ALOITUS_HAKEMISTO=. fi if ! test -d $ALOITUS_HAKEMISTO ; then echo \"$ALOITUS_HAKEMISTO\" ei ole hakemisto tai sitä ei ole olemassa. Loppu. exit 1 fi find $ALOITUS_HAKEMISTO -name \*.jpg | while read image ; do identify $image ; done | cut -d ' ' -f 3 | sort | uniq -c
Kommentit ovat hyviä, mutta älä kirjoita niitä liikaa. Yritä kirjoittaa ohjelma niin, että koodi itsessään on selkeää. Syy tähän on yksinkertainen: ensi vuonna, kun joku muu muuttaa skriptiäsi, tuo toinen henkilö voisi hyvinkin muuttaa komentoja ja unohtaa kommentit, mikä tekee jälkimmäisesti harhaanjohtavaa. Ajattele tätä:
# laske kolmeen for n in `seq 1 4` ; do echo $n ; done
Kumpi se on? Kolme vai neljä? Ilmeisesti ohjelma laskee neljään, mutta kommentin mukaan se laskee kolmeen. Voisit ottaa sen kannan, että ohjelma on oikeassa ja kommentti on väärässä. Mutta entäpä, jos ohjelman kirjoittanut henkilö tahtoi laskea kolmeen ja tämä on syy siihen, miksi kommentti on olemassa? Yritetäänpä näin:
# On kolme pikku porsasta for n in `seq 1 3` ; do echo $n ; done
Kommentti dokumentoi syyn, jonka vuoksi ohjelma laskee kolmeen: se ei kuvaa ohjelman toimintaa, se kuvaa, mitä ohjelman tulisi tehdä. Kokeillaanpa erilaista lähestymistapaa:
PORSAITA =3 for pig in `seq 1 $PORSAITA` ; do echo $porsas ; done
Sama tulos, hieman erilainen ohjelma. Jos uudelleenmuotoilet ohjelmasi, voit tehdä niin ilman kommentteja (sivuhuomautuksena hieno sana tämän kaltaisille muutoksille, joita olemme tehneet, on uudelleenfaktorointi, mutta se on tämän kirjan laajuuden ulkopuolella).
Nykyisessä ohjelmassamme on taikanumero, numero joka saa ohjelman toimimaan, mutta kukaan ei tiedä miksi sen täytyy olla juuri tuo numero. Se on taikuutta!
... cut -d ' ' -f 3 | ...
Sinulla on kaksi vaihtoehtoa: kirjoita kommentti ja dokumentoi miksi sen täytyy olla "3" eikä "2" tai "4", tai lisää muuttuja, joka selittää sen nimellään. Kokeillaanpa jälkimmäistä:
#!/bin/sh # Tämä skripti raportoi kaikkien nykyisen hakemiston tai parametrinä annetun # hakemiston alla olevien JPEG-tieodstojen koon ja jokaisen koon kuvien määrän. if test -n "$1" ; then ALOITUS_HAKEMISTO=$1 else ALOITUS_HAKEMISTO=. fi if ! test -d $ALOITUS_HAKEMISTO ; then echo \"$ALOITUS_HAKEMISTO\" ei ole hakemisto tai sitä ei ole olemassa. Loppu. exit 1 fi KUVAN_KOKO=3 find $ALOITUS_HAKEMISTO -name \*.jpg | while read image ; do identify $image ; done | cut -d ' ' -f $KUVAN_KOKO | sort | uniq -c
Se parantaa noita asioita hieman; ainakin nyt tiedämme mistä 3 tulee. Jos ImageMagick muuttaa joskus ulostulonsa formaattia, voimme päivittää skriptiä vastaavasti.
Lopuksi muttei viimeiseksi, tarkasta ajamiesi komentojen poistumisstatus. Nykyisessä esimerkissämme ei ole paljon mahdollisuuksia epäonnistumiseen. Kokeillaanpa vielä viimeistä esimerkkiä:
#!/bin/sh # Kopioi kaikki HTML- ja kuvatiedostot lähdehakemistosta annettuun kohdehakemistoon. SRC=$1 DST=$2 if test -z "$SRC" -o -z "$DST" ; then cat<
Huomaa, että tässä esimerkissä käytetään monia asioita, jotka olet oppinut tässä kirjassa. Se ei yritä olla täydellinen; voit harjoitella parantelemalla sitä!
Sinun tulisi huomata, että ohjelma kiinnittää huomiota virhetiloihin, joita eri ohjelmat voivat tuottaa. Esimerkiksi se ei vain kutsu komentoa mkdir
nähdäkseen toimiko ohjelma, vaan se tekee näin:
if ! mkdir -p "$DST" ; then echo Ei voi luoda päämäärätiedostoa \"$DST\". Stop. exit 1 fi
Se kutsuu komentoa mkdir
ehtona komennolle if
. Jos mkdir
kohtaa virheen, se poistuu statuksella, joka on jotain muuta kuin 0, ja if
-ehto tulkitsee sen epätodeksi tilaksi. "!" on negaatio-operaattori, ja muuttaa sen epätoden todeksi (tai päinvastoin). Niinpä rivi sanoo periaatteesa "aja komento mkdir, muuta virhe arvoksi tosi operaattorilla '!', toimi mikäli virhe on olemassa." Lyhyesti sanottuna, mikäli mkdir kohtaa virheen, ohjelmavirta menee if-lauseen vartaloon. Tämä voisi tapahtua esimerkiksi mikäli käyttäjällä, joka ajaa skriptin, ei ole oikeuksia luoda pyydettyä hakemistoa.
Huomaa myös merkkien "&&" käyttö varmistamaan virhetilat:
mkdir -p "$DST/$dir" && cp -a "$filename" "$DST/$filename"
Jos
mkdir
epäonnistuu, komentoa cp
ei kutsuta. Edelleenkin, mikäli joko mkdir tai cp epäonnistuu, poistumisstatus on jotain muuta kuin 0. Tuo ehto tarkastetaan seuraavalla rivillä:
if test $? -ne 0 ; then
Koska tämä voisi osoittaa jonkin epäonnistuvan hirvittävällä tavalla (esimerkiksi levy on täynnä), meidän on paras luovuttaa ja lopettaa ohjelma.
Skriptien kirjoittaminen on taidetta. Sinusta tulee parempi taiteilija tarkastelemalla edeltäjiesi työtä ja tekemällä paljon itse. Toisin sanottuna: lue paljon skriptejä ja kirjoita itse paljon skriptejä.
Hauskaa hakkerointia!
There has been error in communication with Booktype server. Not sure right now where is the problem.
You should refresh this page.