Hieman monipuolisempi skripti

ITKA203 Käyttöjärjestelmät -kurssin Demo 4 keväällä 2014. "Hieman monipuolisempi skripti"

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

Jyväskylän yliopiston tietotekniikan laitos.

Contents

Mistä tässä harjoitteessa on kyse

Tässä ensinnäkin julkaistaan Bourne Shellin pikaohje, joka tulee tentissä liitteeksi. Perinteinen tenttitärppi on jonkinlaisen pienen shell-skriptin kirjoittaminen. Tavoitteita tässä harjoituksessa ovat seuraavat:

  • Näet ja jopa jonkin verran teet skriptiohjelmointia, mikä toivottavasti valaisee mahdollisuuksia ja käyttötarkoituksia moiselle.
  • Kurssilla teoriassa nähdyt asiat (mm. muistinhallinta, tiedostojärjestelmä, shell ja skriptaus) konkretisoituvat pienoisen soveltamisen kautta.

Skriptit

Tuolla on lisätietoa; hyvän näköinen intro:

Tuolla on myös; kattavan oloinen saitti:

Ja Internetin hakukoneilla löytyy lisää.

Aloita tästä

Tämänkertainen esimerkkikoodi on paketissa demo4.zip kurssin kotisivulla. Siis etäkoneella saat paketin esim. näin:

cd kj14/demo4  # eli siirry omaan demo4:n hakemistoon.
wget http://users.jyu.fi/~nieminen/kj14/demo4/demo4.zip
unzip demo4.zip

Ohjeet:

  • Paketista löytyvä skripti shrakenteita.sh demonstroi ohjelmointirakenteita Bourne Again Shellissä. Ajatus on, että ensiksi tätä esimerkkiä tutkimalla opit, kuinka shellissä voi käyttää alkeellisia ohjelmointirakenteita. Mukana on pieni esimerkki ympäristömuuttujasta (environment variable), jollaisia ohjelmat saavat käyttöönsä (käyttöjärjestelmän kautta) ja joiden perusteella ne... no... saavat tietoja siitä ympäristöstä (asennettujen tiedostojen sijainnit ym.), jossa ne on käynnistetty.
  • Palautustehtävässä sovellat tässä ja aiemmissa demoissa opittuja asioita ja tuotat puolijärkevän skriptin, joka tallentaa aikaleimattuna käyttöjärjestelmän toimintatilaan liittyviä tietoja mahdollista myöhempää prosessointia varten.

Huomioita ja muistelua aiemmin läpikäydyistä asioista:

  • Skriptit ajetaan kuin ne olisivat tavallisia ohjelmia. Niinpä voit ajaa esim. työ-/oleskeluhakemistossa olevan skriptin shrakenteita.sh kirjoittamalla komennon:

    ./shrakenteita.sh
    
  • Skripteillä täytyy tällöin olla suoritusoikeus. Tuossa nettisivulta haetussa paketissa ne on valmiiksi asennettu, ja komennolla chmod voidaan oikeuksia muutella tarvittaessa.

  • Skriptin ensimmäinen rivi alkaa #! kertoen, millä tulkkiohjelmalla skripti on ajettava. Käyttöjärjestelmä huomioi tiedoston kaksi ensimmäistä tavua ns. "taikakoodina" ja jos ne ovat merkit #! toimitaan eri tavoin kuin konekielisten ohjelmien kanssa. Tässä demossa tulkiksi ladataan ohjelmatiedosto nimeltä /bin/bash eli Bourne Again Shell -- lisäksi kannattaa bashiä käyttäessä laittaa "turvaverkoksi" sen ymmärtämä argumentti -eu, jolloin skriptin ajo loppuu välittömästi, jos jokin sen komennoista päättyy virheeseen (e) tai jos jokin käytetyistä muuttujista ei ole asetettu (u). Lopetus on yleensä turvallisempaa kuin skriptin suorittaminen eteenpäin epäonnistuneen operaation jälkeen. Esimerkissä on käytössä vain -e, mutta voit kokeilla kuinka -eu on erilainen (kaatuu rivillä 44, mikäli käyttäjältä ei tullut vähintään kahta argumenttia; silloin argumentteja vastaavia muuttujia ei nimittäin ole asetettu).

  • Skriptejä voi kirjoittaa millä tahansa tulkattavalla kielellä, esim. Pythonilla ja monen suosimalla Perlillä. Olennaisesti unix-tyyppinen käyttöjärjestelmä lataa minkä tahansa #! -rivillä mainitun ohjelman ja antaa skriptitiedoston sille syötteeksi. Käytämme tässä demossa bashiä, koska se on melko stabiili, hyväksi todettu ja usein perusasennukseen kuuluva. Mm. Python tai Perl eivät välttämättä ole aina saatavilla ilman erillistä asennusta.

Selaa lämmittelyn vuoksi huvikseen läpi tiedostoa /etc/rc.sysinit IT-palveluiden suorakäyttökoneella. Ei tarvitse ymmärtää kaikkea; katsele vähän kommentteja ja yleiskuvaa, miltä oikea shell-skripti näyttää "tositoimissa". Kyseinen skripti ajetaan kerran siinä vaiheessa kun Red Hat Enterprise Linux käynnistyy. Käyttöjärjestelmäytimen koodi on silloin jo toiminnassa, mutta skripti mm. ajaa ylös useita palveluita, jotka toimivat omina prosesseinaan. Ohjelman tuloste löytyy tiedostosta /var/log/boot.log - löytänet yhtymäkohtia koodin ja tulosteen välillä. Havaitaan, että kyseinen skripti on kohtalaisen tärkeä tietokoneen ylösajon kannalta; siellä mm. käynnistetään ssh-palvelu, joka ottaa vastaan salattuja etäyhteyksiä.

Useimpiin tarkoituksiin skripteissä ei kannata yrittääkään tehdä kaikkea shellin rajallisilla ohjelmointiominaisuuksilla, vaan kannattaa käyttää apuohjelmia. Shellille annettu komentorivihän vastaa olennaisesti ohjelman käynnistämistä, kuten aiemmin on nähty. Täytyy tosin muistaa myös, että kaikkia apuohjelmia ei ole asennettu kaikkialle, ja niiden eri versiot voivat erota toisistaan esim. argumenttien muodon suhteen. Yhteensopivien ja alustariippumattomien skriptien tekeminen vaatii siis pientä tarkkuutta.

Katsotaan tässä yhteydessä kahta kätevää työkalua: find ja grep. Yhdessä nämä ohjelmat tarjoavat samanlaista toiminnallisuutta kuin esim. Windowsin etsintätyökalu. Mm. näitä komentorivityökaluja voi käyttää skripteissä.

Esim: apuohjelma find

Find etsii tiedostoja argumenttina annettujen ehtojen mukaisesti. Se on asennettu useisiin unixeihin. Tässä henkilökohtaiseen tietoturvaan liittyvä esimerkki:

#!/bin/bash -eu
find $HOME -maxdepth 1 -type d ! -name ".*" | \
while read a
do
  chmod go-rwx $a
done

Tämän skriptin find -komento etsii kotihakemistossa ($HOME) olevat hakemistot (-type d), joiden nimi ei ala pisteellä (! -name ".*", jättää siis piilotiedostot huomioimatta), ja syöttää tulosteensa putkella sh-shellin while -toistorakenteelle. Jokaisen findin löytämän hakemiston kohdalla poistetaan hakemistolta kaikki oikeudet kaikilta muilta kuin käyttäjältä itseltään (chmod go-rwx). Find rajoittaa tiedostojen etsimisen kotihakemiston sisältämiin hakemistoihin menemättä alihakemistoihin (-maxdepth 1). Tämä nyt oli vain yksi esimerkki...

Esim: apuohjelma grep

Grep on toinen kätevä, aika usein saatavilla oleva apuohjelma. Sillä etsitään tiedostoista rivejä, joilla on tietynlaista tekstiä. Esimerkiksi seuraava rivi voisi liittyä tekstitiedostona palautetun harjoitustyön tarkastamiseen jollakin kurssilla:

# Tulostetaan opiskelijan tuottamat vastausrivit:
grep -n -B2 -A3 "HARKKA:" $1

Eli grepillä voi etsiä tekstitiedostoista rivejä, joilla on merkkijono. Hyödylliseksi grepin käyttö muuttuu siinä vaiheessa, kun ymmärretään, että sillä voi etsiä täsmällisen merkkijonon sijasta sanarunkoja. Esimerkiksi rivit, joilla on HAR, sitten mitä tahansa merkkejä ja ainakin yksi kaksoispiste:

grep "HAR.*:" fname.txt

Tai itse asiassa mitä tahansa ns. säännöllisten lausekkeiden mukaisia merkkijonovastaavuuksia:

grep "[Hh]a[r]*joitus[t T]*yö[A-Za-zöäåÖÄÅ]*:" fname.txt

Em. komento löytäisi rivit, joilla on esimerkiksi jokin seuraavista merkkijonoista:

Harjoitustyö:
harjoitustyö:
Hajoitustyö:
harrrrrrjoitus yöllä:
Harjoitus työläs:
...

[reunahuomautus: Säännölliset lausekkeet (regular expressions, "RegEx") kannattaa jossain vaiheessa opetella, jos ei vielä ole tulleet vastaan. Niitä käytetään mm. Javan merkkijonoluokkien match -metodeissa ja ylipäätään aika monessa paikassa. Ne muuttuvat vielä tehokkaammiksi siinä vaiheessa, kun oppii käyttämään ns. ryhmiä (groups) joiden avulla voi korvata regexpin osien sisältöjä uudella tekstillä, toisillaan tai vaihtaa niitä keskenään, tai ylipäätään ottaa niitä irti tietorakenteisiin käsittelyä varten. Säännöllisten lausekkeiden teoriaa ja koneiston toteutusta meillä sisältyy kurssiin nimeltä Automaatit ja kieliopit. Niiden peruskäyttely "tekstinkäsittelytehtävien sveitsiläislinkkarina" kuuluu IT-yleissivistykseen.]

Kaikki on tiedosto -paradigma

Etäkäyttökoneisiimme asennettu Linux toteuttaa Unixin perusideaa, että levyllä sijaitsevien tiedostojen lisäksi monet muutkin asiat näyttäytyvät tiedostomaisesti samassa hierarkiassa. Laitteisiin, kuten kovalevyihin ja päätteisiin, pääsee (käyttöoikeuksien määrittelemissä rajoissa) käsiksi suoraan hakemiston /dev/ alta. Hakemiston /proc/ alta puolestaan voi lukea yksityiskohtaista tietoa yksittäisten prosessien sekä käyttöjärjestelmän hallinnoimien resurssien tilasta.

Esimerkiksi prosessorien tiedot saa tulostettua komennolla:

cat /proc/cpuinfo

Yleistä tietoa muistinkäytöstä saa komennolla:

cat /proc/meminfo

Varsin yksityiskohtaista logi- ja tilannetietoa sivuttavan muistinhallinnan käytöstä löytyy täten:

cat /proc/vmstat

Yksityiskohtia tulostuu /proc/vmstat -tiedostosta aika monta, joten tarve voi olla poimia tekstihaun avulla vain tietty kenttä (tai useampi). Esimerkiksi voisi poimia rivin, jolle on laskettu järjestelmän käynnistämisen jälkeen tapahtuneet sivunvaihtokeskeytykset:

cat /proc/vmstat | grep pgfault

Reunahuomautuksena pedanteille tarkkailijoille: yllä oleva esimerkkikomento putkittaa vain ollakseen jälleen yksi esimerkki putkittamisesta. Grep osaisi ottaa tiedoston argumenttinakin, ja tuollainen "useless use of cat" pitäisi tietenkin oikeassa käytössä korvata komennolla:

grep pgfault /proc/vmstat

Erikoistiedostot näyttävät siis samalta kuin vaikkapa tekstitiedostot, joten niitä voi käyttää samoilla apuohjelmilla, vaikka sisältö generoituukin uudelleen aina luettaessa. Käytännön sovelluksena tehdään seuraavaksi oma täsmäkirjanpito-ohjelma tietyille tiedosto-osoitteesta /proc/meminfo saataville tilannetiedoille.

Pakollinen palautustehtävä

Tee ja palauta optimaan skripti nimeltä demo4.sh, jonka tulee toimia seuraavin tavoin:

  • Skripti ei saa toimia ilman argumentteja, vaan argumenttien puuttuessa se tulostaa käyttöohjeet ja päättää toimintansa nollasta poikkeavaan virhekoodiin.

  • Skriptin ensimmäinen argumentti on oltava käyttäjän valitsema tiedostonimi, johon skriptin on määrä tulostaa tietoja.

  • Mikäli käyttäjä antoi ainoastaan tiedostonimen, mutta tätä tiedostoa ei ole vielä olemassa tai siihen ei ole kirjoitusoikeutta, skripti ilmoittaa tästä kohteliaasti ja päättää toimintansa nollasta poikkeavaan virhekoodiin.

  • Mikäli käyttäjä antoi toisena, jälkimmäisenä, argumenttina merkkijonon --create, skripti luo ensimmäisellä argumentilla pyydetyn tiedoston (tuhoten mahdollisen aiemman sisällön) ja kirjoittaa tiedostoon ainoastaan otsikon Muistinkäyttölogi. Sen jälkeen skripti päättää toimintansa virhekoodilla nolla (eli "kaikki hyvin").

    [Oikeasti pitäisi tietysti tarkistaa että kirjoitus todellakin onnistui, mutta mennään nyt siitä missä aita on matala... kuten on silloin myös tuotoksen käyttömukavuus ja vikasietoisuus.]

  • Mikäli käyttäjä antoi enemmän kuin yhden argumentin, mutta toinen argumentti ei ole juuri tuo vaadittu --create, niin skripti tässäkin tapauksessa antaa käyttäjälle ohjeet ja päättyy nollasta poikkeavaan virhekoodiin.

  • Muussa tapauksessa, eli kun käyttäjä antoi ainoastaan yhden argumentin, joka vieläpä osoittaa olemassa olevaan ja kirjoitusoikeuksin varustettuun tiedostoon, niin skripti lisää kyseisen tiedoston loppuun kaksi tulostetta:

    • Aikaleima siten kuin komento date sen antaa
    • Erikoistiedostosta /proc/meminfo ne rivit, joista löytyy merkkijono Free (tähän käytä siis apuohjelmaa grep)

Kokeile, että skripti toimii pyydetyllä tavalla kaikissa erikoistapauksissa, eli:

  • antaa käyttäjälle ohjeita ja lopettaa kesken, jos argumentit eivät ole OK
  • tyhjentää ja alustaa pyydetyn logitiedoston esim. komennolla ./demo4.sh testailua.log --create
  • täydentää logiin uuden aikaleiman ja hetkellisen vapaan muistin määrät komennolla ./demo4.sh testailua.log

Tässä käytellään melkeinpä alkeellisimpia Bourne (Again) Shellin perusrakenteita eli ehtolausetta ja argumenttina tulleita muuttujia. Tekeminen edellyttää myös aiemmista demoista tuttujen komentojen ja tiedostoon ohjaamisen soveltamista. Ohjausta saa tilaisuuksissa tarvittaessa, ja netti on pullollaan materiaalia.

Varaudu tentissä ymmärtämään ja/tai tekemään noin 5-12 rivin mittainen skripti, jossa voi tarvita mitä tahansa alla olevassa liitteessä esiteltyä ohjelmointirakennetta (liite siis on mukana myös tenttikysymyspaperissa, mikäli tenttiin tulee skriptitehtävä). Kannattaa tutustua erikseen ainakin silmukkarakenteiden toimintaan, koska niihin ei tullut omatoimista kokemusta tämän demon pakollisessa palautuksessa. [Vapaaehtoisessa "edistynyt skripti" -demossa sovelletaan laajemmin ja varaudutaan käsittelemään mahdolliset virhetilanteet paremmin.]

Loppuhuomioita:

  • Tässä samalla voit havainnoida etäkoneen muistinkäyttöä ajamalla itse tehdyn skriptin muutamia kertoja ja catsomalla (cat, heh..) tulostunutta logia. Havaintona on luultavasti se, että vapaan muistin määrä vaihtelee vinhaan tahtiin sen mukaan, millaisia dynaamisia muistivarauksia ja -vapautuksia kymmenien käyttäjien erilaiset ohjelmat milloinkin tekevät. [Vapaaehtoisessa "huonosti käyttäytyviä ohjelmia" -demossa katsotaan miten käy, jos ohjelmat vahingossa tekevät vain varauksia, eivätkä vapautuksia...]
  • Vastaavalla tavoin järjestelmän ylläpitäjä voisi kerätä aikaleimattua tietoa levytilasta, prosessorien käyttöasteesta, käyttäjämääristä tai muusta mitä milloinkin on tarpeen seurata. Yleisimpiä seurattavia varten on tietysti olemassa helppokäyttöisiä profilointi- ja analysointityökaluja, jotka konepellin alla toimivat olennaisesti samalla tavoin kuin tässä tehty skriptihärpäke. Sama periaate (logiohjelma kysyy käyttöjärjestelmältä tilannetietoja ja tallentaa ne analysointia varten) on takana kaikkialla, missä nähdään tietokoneen resurssihistoriaa graafeina, esimerkiksi Windowsin Task Managerin "CPU Usage History" ja "Physical Memory Usage History" -näkymät.

Liite: Yhden sivun sh-luntti

Alla on tentinkin liitteenä jaettava lunttilappu, jossa esimerkkien kautta muistutetaan joistakin Bourne Shellin piirteistä. HUOM: Tarkoituksella tenttiluntissa ei tulla mainitsemaan sitä, että skriptin ensimmäisellä rivillä pitää kertoa, millä nimenomaisella shellillä se ajetaan... Se pitää kuitenkin vastauksessa olla, oikealla syntaksilla ja siitä paikasta, josta yleensä Bourne Again Shell -ohjelma löytyy eli #!/bin/bash. Siitä tietää, onko aihetta yhtään opeteltu, mietitty ja tehty, vai yritetäänkö vaan sommitella palikoita paikoilleen kylmiltään... Parhaimmillaan laitamme tosiaan argumenttien -eu kanssa (ks. grep-esimerkki yllä) eli ensimmäiseksi riviksi aina #!/bin/bash -eu

Joitakin ohjelmointirakenteita:
    muuttuja=57               # tarkka syntaksista: ei välilyöntejä!
    muuttuja="eki"; muuttuja="$muuttuja$muuttuja"
    read muuttuja             # lukee muuttujaan syöttövirrasta

    if EHTO                   # Myös:  if EHTO; then ...; \
    then                      #        elif EHTO; then ...; fi
      ...                     #
    fi                        #

    for muuttuja in LISTA     # muttujan "esittelyssä"/asetuksessa ei $
    do
      ... jotakin $muuttuja jotakin ... # muuttujan käytössä $muuttuja
    done

    while EHTO; do ... ; ... ; done     # käskyerotin rivinvaihto tai ;

    # Tee jotain syötteen kaikille riveille:
    while read rivi; do echo "luin: $rivi" ; done < rivit.txt

    case $hanen_nimensa in
      kirsimarja)
          echo "Hei Kippe" ;;
      eskomatias)
          echo "Moi E.M." ;;
    esac

    Aliohjelmat mahdollisia&hyödyllisiä, mutta ei käsitellä ITKA203:lla.

Joidenkin ehtojen käyttöä (Välilyönnit merkityksellisiä! "[" on itse
asiassa komento ja loppuosa on sen argumenttilista!):
    [[ -d TIEDOSTONIMI ]]      # tosi, jos on hakemisto
    [[ -f TIEDOSTONIMI ]]      # tosi, jos on tavallinen tiedosto
    [[ -w TIEDOSTONIMI ]]      # tosi, jos on olemassa ja kirjoitettavissa
    [[ ! -f TIEDOSTONIMI ]]    # tosi, jos ei olemassa tai ei tavallinen
    [[ "$muuttuja" -le "57" ]] # tosi, jos muuttuja <= 57 (myös lt,gt,ge)

Argumenttien käyttö:   (yli 9 arg mahd., mutta ei käsitellä ITKA203:lla)
    echo "Tämän skriptin nimi on $0. Eka argumentti $1, neljäs $4"
    echo "Argumenttien määrä on $#"

Joitakin sisäänrakennettuja toimintoja:
    exit VIRHEKOODI              # koodi 0 tarkoittaa onnistumista
    cd                           # vaihtaa skriptin työhakemistoa
    echo                         # kaiuttaa tekstiä
    let muuttuja=$muuttuja+3     # (perusaritmetiikkaa)

Erikoismerkkejä (tuttuja interaktiivisesta käytöstä; toimii skripteissä):
    <   >   >>   |   `KOMENTO`