Edistyneempi skripti

ITKA203 Käyttöjärjestelmät -kurssin demo keväällä 2014. "Hieman monipuolisempi skripti" - kuudes "vapari" keväällä 2015.

Paavo Nieminen, paavo.j.nieminen@jyu.fi

Jyväskylän yliopiston tietotekniikan laitos.

Sisällys

Mistä tässä harjoitteessa on kyse

Harjoituksen tavoitteet:

  • Tutustut shell-skriptaukseen syvemmin kuin mitä pakollisissa demoissa tehtiin, erityisesti aliohjelmien tekemiseen ja argumenttien käyttöön tutustutaan.

  • Teet itse skriptin, joka tekee jotakin puolijärkevää ja käyttäytyy hyvin, erityisesti:

    • skripti käyttää väliaikaista tiedostoa taiteen sääntöjen mukaisesti
    • skripti varautuu siivoamaan jälkensä, jos sen suoritus lopetetaan ulkopuolelta
    • skripti yrittää olla tuhoamatta lopullisesti käyttäjän dataa, vaikka se pyrkii olemaan suora tehotyökalu.

Ohjeita ja tarvittavia palasia

Tässä luvussa käydään läpi tarvittavat palaset suomen kielellä. Valmista vastausta ei anneta, koska soveltaminen on olennaista. Sisällöt mukailevat demo4:ssä mainittuja englanninkielisiä ohjeita:

Simuloitua testiaineistoa voi hakea tuosta paketista:

Perusperusteet

Joko linkitetyistä tutoriaalista tai aiemmista demoista oletetaan tutuksi:

  • Shellin perusidea: suoritus riveittäin ellei sisäänrakennetuilla ohjausrakenteilla ole muuta ilmaistu
  • muuttujien asettamisen ja käytön syntaksi
  • yksinkertaiset ehtolauseet
  • argumenttien käyttö shell-ohjelmassa

Lisähuomautus paikallaan: Käytämme bashiä, jonka ominaisuudet (mm. [[ ... ]] -ehtolauseet) ovat hieman laajemmat kuin sen esi-isällä Bourne Shellillä. Periaatteessa kaikkein portaabeleimmat skriptit eivät käytä bashin hienouksia, ja siksi esim. tässä linkitetty tutoriaali pitäytyy alkuperäisen sh:n syntaksissa, jossa mm. ehdot ovat mallia [ ... ], operaattoria == ei käytetä jne. Toteutettava esimerkkiohjelma on niin yksinkertainen, ja toisaalta sen tarvitsee toimia vain bashissä, ettei ole niin väliä, kuinka pedantisti ominaisuuksia käyttää. Kunhan skripti toimii, eikä tee liikaa tyhmyyksiä :).

Apuohjelma iconv

Tässä demossa tehdään "puolijärkevä" skripti toimenpiteeseen, jota ainakin luennoitsija on tarvinnut siirtäessään tämän kurssin materiaaleja ns. "latin-9" -merkistöllä kirjoitetusta formaatista nykyaikaisempaan "utf-8" -merkistöön. Yhdelle tiedostolle operaatio voidaan tehdä apuohjelmalla iconv seuraavasti:

iconv -f latin-9 -t utf-8 tiedosto.txt

Lisätietoja luonnollisesti komennolla man iconv. Mahdolliset koodaukset saa listattua optiolla iconv -l.

Ohjelma operoi yhdelle tiedostolle kerrallaan, ja tulostaa suoraan standardiulostuloon. Tulee siis usein komennettua vaikkapa seuraavanlaisesti:

mv tekstiA.txt tekstiA.txt.alkup
iconv -f latin-9 -t utf-8 tekstiA.txt.alkup > tekstiA.txt

mv tekstiB.txt tekstiB.txt.alkup
iconv -f latin-9 -t utf-8 tekstiA.txt.alkup > tekstiA.txt

Näiden komentojen jälkeen on kokonaista kaksi tiedostoa muutettu merkistöstä toiseen, ja varmuuden vuoksi alkuperäinen tiedosto on tallessa, jos vaikka muunnos olisi sotkenut jotakin. Kirjoitusvirheen mahdollisuus on suuri, kun komentoja tulee tehtyä copy-pastella. Lisäksi liiasta kirjoittamisesta voi seurata hermoratavaurioita käsiin. Pidemmän päälle tekisikin mieli pystyä komentamaan suoraan usealle, vaikkapa sadalle, tiedostolle:

muunna latin-9 utf-8 kjdemot/*.txt

Ja siitäpä idea koko tähän "puolijärkevään" harjoitukseen: Skriptit ovat omiaan mm. laajentamaan yhdelle tiedostolle operoivan perusohjelman toimintaa käyttäjän tarvitsemaan laajempaan tarkoitukseen. [Reunahuomautuksena tein joskus itselleni mm. skriptin, jolla aina bänditreenin jälkeen muunnan nauhoitettujen raitojen raaka-audion 48kHz -formaatista 44.1kHz -formaattiin sekä mp3-muotoon bändin sisäistä jakelua varten. Kyseinen skripti on ollut käytössä vuosikausia ... olisinpa tehnyt sen aikoinaan näitä ohjeita noudattaen "hyvin käyttäytyväksi" ...]

Väliaikaistiedostot ja apuohjelma mktemp

Skripteissä, kuten muissakin ohjelmissa, saatetaan tarvita väliaikaistiedostoja, joiden on hyvä sijaita tarkoitusta varten järjestelmään asennetusta hakemistosta, johon kaikilla käyttäjillä on kirjoitusoikeus. Esimerkiksi yliopistomme suorakäyttökoneilla se on hakemisto /tmp/ ja mikroluokkien PC-koneissa C:\MyTemp. Väliaikaistiedoston idea on, että sitä ei tarvittaisi enää ohjelman päättymisen jälkeen, eli hyvin käyttäytyvä ohjelma huolehtii nämä aputiedostonsa pois suorituksen päättyessä (myös, kun suoritus päättyy virheeseen tai pakkolopetukseen!). Monet ohjelmat käyttävät väliaikaistiedostoja automaattitallennuksiin, välimuistitukseen, ynnä muuhun loppukäyttäjän elämää helpottavaan toimintaan.

Väliaikainen tiedosto pitäisi nimetä sillä tavoin, että mikään muu saman tai toisen käyttäjän suorittama ohjelma ei yritä käyttää juuri samaa tiedostoa. Tiedoston nimi ei siis kannata olla mallia /tmp/temppi vaan se voisi koostua vähintäänkin esimerkiksi käyttäjänimestä, prosessi-id:stä ja kellonajasta.

Yleis-unixmaisempiakin keinoja löytyy esim. Parkerin tutoriaalista, mutta tässä harjoituksessa käytetään GNU-apuohjelmaa nimeltä mktemp, joka luo sopivan satunnaisesti nimetyn tiedoston asianmukaiseen paikkaan ja vasta luonnin onnistuttua ilmoittaa luodun väliaikaistiedoston nimen. Luonnollisesti man mktemp kertoo lisää. Nimen luontia voi kokeilla optiolla --dry-run, jolloin luodaan vain nimi eikä itse tiedostoa:

mktemp --dry-run

Jos kokeillessasi vahingossa luot väliaikaistiedostoja, olisi kohteliasta myös poistaa ne. Yhteiskäyttöiseltä palvelimelta voit etsiä omia väliaikaistiedostojasi vaikkapa (jo aiemmin tutuksi tulleella) komentoyhdistelmällä:

ls -l /tmp/ | grep `whoami`

Osa näistä tiedostoista on todennäköisesti muiden käynnissä olevien ohjelmien tekemiä, joten pidä huolta, ettet vahingossa sekoita niiden toimintaa poistamalla vielä niiden käytössä olevia väliaikaistiedostoja.

Mielivaltainen määrä argumentteja

Skriptin argumentteja on tähän asti käytelty malliin $1, $2. Tällä tavoin ei onnistu yli yhdeksän kappaleen lista esimerkiksi tiedostoja, joille skriptin halutaan operoivan. Useampia argumentteja voidaan kuitenkin käyttää yksi kerrallaan. Komento shift poistaa ensimmäisen (siis entinen $1) ja siirtää kaikkia argumentteja yhdellä pienempään numeroon. Seuraava esimerkki (suoraan Steve Parkerin tutoriaalista) tulostaa kaikki argumentit, olipa niitä kuinka monta tahansa. Silmukan loppuehtona on, että lukumäärä $# putoaa nollaan toistuvien siirtojen vaikutuksesta:

#!/bin/sh
while [ "$#" -gt "0" ]
do
  echo "\$1 is $1"
  shift
done

Komennon onnistumisen kokeileminen

Bashille annettava optio -e lopettaa skriptin suorituksen, jos mikä tahansa komento siinä epäonnistuu (eli päättyy nollasta poikkeavaan exit-koodiin). Tällöin skripti ei kuitenkaan pysty käsittelemään tilannetta mitenkään hienommin, esimerkiksi tulostamaan omaa ilmoitusta käyttäjälle. Hyvin käyttäytyvä skripti ei käytäkään valitsinta -e vaan varautuu virheisiin tarkistamalla edellisen komennon exit-koodin erikoismuuttujasta $? kuten seuraavassa esimerkissä (edelleen suoraan Steve Parkerin tutoriaalista):

#!/bin/sh
/usr/local/bin/my-command
if [ "$?" -ne "0" ]; then
  echo "Sorry, we had a problem there!"
fi

Aliohjelmat (eli funktiot)

Shell-skriptissä voi käyttää myös aliohjelmia (joita ilmeisesti sanotaan skriptien yhteydessä "funktioiksi"). Aliohjelmien määrittelyssä ei nimetä parametreja, vaan ne tulevat aliohjelman käyttöön ikään kuin skriptin argumentit "pääohjelmalle". Esimerkki syntaksista ja semantiikasta:

#/bin/sh
jonkinlainen_aliohjelma()
{
    echo "Olen aliohjelma.."
    echo "Eka parametrini on $1"
    echo "Toka parametrini on $2"
    echo "Aion palauttaa kokonaisluvun (muuta en voisikaan) 123"
    return 123
}

# Varsinainen skripti tapahtuu aliohjelmamäärittelyjen jälkeen, koska
# muuten rivi riviltä toimiva shell ei voisi aliohjelmasta mitään
# tietää:

echo "Kutsun nyt aliohjelmaa parametreilla kissa, koira"

jonkinlainen_aliohjelma kissa koira

echo "Paluuarvo oli $?"
echo "(Ikään kuin olisin suorittanut ulkopuolisen ohjelman, jolla oli exit-koodi)"

jonkinlainen_aliohjelma aasi apina

echo "Kutsuin uudelleen. "
echo "Paluuarvo on $?, mutta nyt se on jo echo-komennolta!!"

Kryptisen määrittelysyntaksin ja parametrinvälityksen lisäksi muuttujien näkyvyysalue ja sivuvaikutukset skripteissä ovat erilaiset kuin mihin normaali C#/Java/C -ohjelmoija on tottunut. Todetaan ainakin, että globaalit, ohjelman alussa määritellyt, muuttujat ovat saatavilla joka paikassa. Missään nimessä ei käsitellä asiaa tässä enempää, vaan käytetään korkeintaan em. esimerkin mukaista parametrin ja paluuarvon välittämistä ( hurjemmin kiinnostuneet lukekoot vaikkapa Parkerin ohjeesta funktioita käsittelevän kohdan http://steve-parker.org/sh/functions.shtml )

Signaalien poiminta

Kun tehdään väliaikaisia tiedostoja (tai avataan muita resursseja, jotka voisivat jäädä roikkumaan skriptin loppuessa yllättävästi kesken), on syytä tehdä siivoustoimenpiteitä. Näitä varten voi olla esimerkiksi aliohjelma nimeltään cleanup (tai siivoa tai vastaavaa :)), ja tämä aliohjelma pitää kytkeä käsittelemään ulkopuolelta tulevat lopetussignaalit. Esimerkki:

#!/bin/sh
temppitiedosto=`mktemp`
if [ "$?" -ne "0" ]
then
    echo Väliaikaistiedostoa ei voitu luoda. Lopetan.
    exit 1
fi


trap cleanup SIGHUP SIGINT SIGQUIT SIGABRT

cleanup()
{
  echo "Lopetetaan nätisti ja siivotaan jäljet."
  rm $temppitiedosto
  exit 1
}

echo "Tiedosto $temppitiedosto on tämän skriptin käyttöön."
echo "Tätä tekstiä ei haluta säilyttää" >> $temppitiedosto

Vaatimukset

Skriptisi toimii seuraavalla tavalla:

Lisäksi skriptisi on toteutettu seuraavalla tavoin:

VAROITUS1: Ota huomioon, että kehittäessäsi skriptiä se ei välttämättä toimi vielä niin kuin sen lopulta pitää, joten testaa turvallisella aineistolla, älä esim. kandityösi käsikirjoituksella. (Ja pidä aina huolta varmuuskopioista jne., jne., aina se sama saarna...)

VAROITUS2: Skripti tekee väliaikaistiedostoja yhteiskäyttöiseen hakemistoon, ja niin kauan kuin se ei vielä osaa poistaa niitä nätisti, voi yhteiskäyttökoneelle kertyä turhaan tiedostoja, jotka vievät tilaa - eivät ehkä kovin paljon, mutta ylimääräistä se on pienikin ylimäärä. Jos epäilyttää, ettei homma pysy näpeissä, tee tämäkin demo kurssin testipalvelimella itka203.it.jyu.fi, jossa ei voida häiritä muita.

Vapaaehtoinen palautustehtävä (1p)

Palautat skriptin nimeltä "muunna_merkit.sh", joka toimii vaatimusten mukaisesti.