Tekijä: Jaakko Kajaste, jkajaste@cs.stadia.fi
16.2.2002
Tässä dokumentissä käsitellään komentoriviohjelmointia. Komentotulkkina käytetään Bashia eli Bourne-Again SHelliä.
Shell-skriptien avulla on kätevä automatisoida usein suoritettavia monimutkaisia ja miksei yksinkertaisiakin tehtäviä.
Skriptien ymmärtäminen auttaa myös ymmärtämään Linux-järjestelmien toimintaa, koska monet Linuxin ohjelmat ovat erilaisia
skriptejä. Skriptit sopivat parhaiten pienten tehtävien hoitamiseen. Shell skriptien kirjoittamiseen/ymmärtämiseen
vaaditaan Linuxin peruskomentojen hallintaa, koska skripteissä käytetään näitä työkaluja hyväksi. Kuten monissa muissakin
ohjelmointioppaissa, niin myös tässä tutustutaan ensimmäisenä Hello World-ohjelmaan. Tämä esimerkkiskripti tulostaa ruudulle
tekstin "Hello World":
#!/bin/bash echo "Hello World"Skriptin ensimmäinen rivi keroo missä komentotulkissa skripti suoritetaan ja mistä hakemistosta komentotulkki löytyy. Bash ei välttämättä aina sijaitse /bin-hakemistossa, joten tarvittaessa sen sijainnin voi tarkistaa ainakin which-komennon avulla kirjoittamalla komentoriville:
which bash
#!/bin/bash # tämä ohjelma tulostaa luvut 1-10 for i in 1 2 3 4 5 6 7 8 9 10; do echo $i done
#!/bin/bash luku=10 echo "Muuttujan luku arvo on $luku"Ohjelmassa luku-muuttujalle annetaan arvo 10, jonka jälkeen tulostuu teksti "Muuttujan luku arvo on 10". Muuttujia määriteltäessä ei muuttujan nimen, "="-merkin ja muutujan arvon väliin saa jättää tyhjiä välejä. Yllä olevasta esimerkistä huomataan, että määriteltyä muuttujaa käytettäessä muuttujan nimen eteen laitetaan "$"-merkki.
Paikallinen muuttuja voidaan luoda käyttämällä avainsanaa local. Esimerkki tästä:
#!/bin/bash
HELLO=Hello
funktion hello {
local HELLO=World
echo $HELLO
}
echo $HELLO
hello
echo $HELLO
Ohjelmasta huomataan, että muuttuja HELLO ja paikallinen muuttuja HELLO eivät vaikuta toisiinsa millään tavalla.#!/bin/bash echo $SHELL
$0 Tähän muuttujaan tallennetaan suoritettavan skriptin nimi.
$1-$9 Näihin muuttujiin tallennetaan ohjelmalle annetut parametrit järjestyksessä.
$? Tähän muuttujaan tallennetaan viimeisimmän suoritetun ohjelman exit arvo.
$* Tähän muuttujaan tallennetaan kaikki ohjelmalle annetut argumentit ($1 $2 ...).
$@ Tähän muuttujaan tallennetaan kaikki ohjelmalle annetut argumentit ("$1" "$2" ...).
$# Tähän muuttujaan tallennetaan ohjelmalle annettujen argumenttien määrä.
ls -l > ls-l.txtTämä luo ls-l.txt nimisen tiedoston, jonka sisällöksi tulee "ls -l"-komennon suorituksen tulos.
cat teksti 2> errorOhjelma tulostaa teksti nimisen tiedoston näytölle, jos tiedostoa ei ole olemassa, tästä aiheutuva virheilmoitus ohjataan error nimiseen tiedostoon.
ls -l 1>&2Hakemiston listaus eli "ls -l"-komennon suorituksen tulos ohjataan samaan paikkaan kuin stderr.
cat teksti 2>&1Mahdollinen stderr ohjataan stdout:iin.
ls -l | grep .cppEsimerkissä hakemistolistauksen tuloste ohjataan grep komennolle, joka etsii tulosteesta kaikki rivit, jossa esiintyy pääte ".cpp" ja tulostaa nämä rivit näytölle.
#!/bin/bash # luetaan käyttäjän nimi ja tervehditään käyttäjää echo -n "Mikä on nimesi: " read nimi # tarkistetaan syöttikö käyttäjä nimen: if [ -z "$nimi" ]; then echo "Et kertonut nimeäsi." exit fi echo "Terve $nimi!"Käyttäjän painaessa enter-näppäintä ilman nimen syöttöä ohjelma tulostaa "Et kertonut nimeäsi.", ja ohjelman suoritus lopetetaan. Jos käyttäjä syöttää esim. nimen Matti, ohjelma tervehtii käyttäjää näin: "Terve Matti!". Syötteen lukemista voitaisiin käyttää myös esimerkiksi ohjelmassa, jossa käyttäjän syötettämiä tietoja vietäisiin tietokantaan.
funktio1(){
komentoja
}
funktio1
function funtio1(){
komentoja
}
funktio2(parametri1,parametri2...parametriN){
komentoja
)
funktio2 parametri1 parametri2... parametriN
#!/bin/bash
# funktio uuden käyttäjän luomiseen
luo_kayttaja()
{
echo "Luodaan uusi käyttäjä..."
sleep 2
adduser
}
echo "1. Luo käyttäjä"
echo "2. Exit"
echo "Anna valintasi: "
read valinta
case $valinta in
1) luo_kayttaja # kutsutaan luo_kayttaja()-funktiota
;;
*) exit
;;
esac
Tämän skriptin käyttämiseen tarvitaan rootin oikeudet adduser-komennon käytön takia.
Lainausmerkkejä on kolmenlaisia, joilla on eri merkitys skriptejä kirjoitettaessa.
Käytetään, jos halutaan esimerkiksi määritellä useasta sanasta koostuva merkkijono. Esimerkki tästä:
mkdir hello worldTämän käskyn suorittaminen luo sekä hakemiston "hello", että hakemiston "world". Jos kuitenkin halutaan luoda vaan yksi hakemisto, kirjoitetaan:
mkdir "hello world"Ensimmäisessä esimerkissä hello ja world olivat kaksi omaa argumenttiä. Lainausmerkkien avulla niistä saadaan tehtyä haluttu yksi argumentti, joka koostuu kahdesta sanasta. Näin saadaan luotua hakemisto nimeltä "hello world". Tässä haluaisin kuitenkin korostaa, että tämä oli vain havainnollistava esimerkki ja Linux-maailmassa on suositeltavampaa käyttää tiedoston niminä yhdestä sanasta koostuvia nimiä. Esimerkin hekemiston nimen suositeltavampi muoto olisi esimerkiksi "hello_world".
Käytetään yleensä muuttujien kanssa. Esimerkki tästä:
#!/bin/bash x=5 # alustetaan x:n arvoksi 5 echo "x on $x" echo 'x on $x'Ensimmäinen echo tulostaa "x on 5" ja toinen "x on $x". Myös tätä käytettäessä voidaan luoda edellä esiteltyjä kahdesta sanasta koostuvia tiedoston nimiä.
#!/bin/bash echo "Käyttäjä tunnukseni on `whoami`"Tämä ohjelma tulostaa tekstin "Käyttäjä tunnukseni on Matti".
x=$(expr $x + 1)Sama lause lainausmerkkejä käyttäen:
x=`expr $x + 1`
#!/bin/bash cp /etc/foo . echo "Kopiointi suoritettu"Ohjelma kopioi "/etc/foo"-tiedoston nykyhakemistoon ja tulostaa tekstin "Kopiointi suoritettu". Ohjelmassa ei oteta huomioon onko tiedosto "/etc/foo" olemassa. Jos tiedosto ei ole olemassa järjestelmä tulostaa virheilmoituksen. Ohjelmaa kirjoittaessa kannattaisi huomioida, että tiedostoa "/etc/foo" ei välttämättä ole olemassa. Tämä saadaan hoidettua ehtorakenteen avulla. Tähän palataan seuraavassa esimerkissä. Useat skripteissä käytetyt komennot ovat järjestelmässä olevia käskyjä, joita voidaan käyttää ilman skriptejäkin. Ehtorakenteet taas ovat skriptien ominaisuuksia, joita ei voida normaalien käskyjen tapaisesti käyttää. Tässä on edellinen ohjelma uudelleenkirjoitettuna:
#!/bin/bash if test -f /etc/foo then # tiedosto on olemassa, joten suoritetaan kopiointi # ja ilmoitetaan käyttäjälle onnistuneesta kopioinnista. cp /etc/foo . echo "Kopiointi suoritettu." else # tiedostoa ei ole olemassa, joten ilmoitetaan käyttäjälle # epäonnistuneesta kopioinnista ja lopetetaan ohjelman suoritus. echo "Tiedostoa ei löydy." exit fiOhjelmassa "if test" testaa onko "/etc/foo"-tiedostoa olemassa ja "-f" testaa onko "/etc/foo" tiedosto, jos se on olemassa ja se on tiedosto kopiointi suoritetaan ja annetaan ilmoitus onnistuneesta kopioinnista, muuten tulostetaan virheilmoitus. Kappaleessa 11. on käsitelty lisää kontrollirakenteiden ehtoihin liittyviä optioita. Edellä käytetylle syntaksille on myös vaihtoehtoinen tapa määritellä sama asia. Edellä käytetty tapa oli:
if test -f /etc/foo thenSama voidaan tehdä myös näin:
if [ -f /etc/foo ]; thenJälkimmäistä tapaa käytettettäessä hakasulkeiden yhteydessä välilyönnit pitää kirjoittaa juuri kyseisellä tavalla. Seuraavaksi tässä on luettelon muodossa käyty läpi olemassa olevat ehtorakenteet:
if [ lauseke ] then komentoja fi
if [ lauseke ] then komentoja else komentoja fi
if [ lauseke1 ] then komentoja elif [ lauseke2 ] then komentoja else komentoja fi
case merkkijono in merkkijono1) komentoja;; merkkijono2) komentoja;; *) komentoja;; esacMerkkijonoa verrataan merkkijono1:seen ja sen jälkeen merkkijono2:seen. Jos jompi kumpi näistä on sama kuin merkkijono, niin sen valitun vaihtoehdon jälkeiset komennot suoritetaan. Jos samaa merkkijonoa ei löydy, suoritetaan * jälkeen olevat komennot.
#!/bin/bash x=5 # alustetaan x 5:ksi # katsotaan mikä on x:n arvo: case $x in 0) echo "x:n arvo on 0." ;; 5) echo "x:n arvo on 5." ;; 9) echo "x:n arvo on 9." ;; *) echo "Tunnistamaton arvo." esacOhjelmassa katsotaan onko x:n arvo 0, 5 tai 9. Jos x:n arvo on joku edellä mainituista, luku ilmoitetaan käyttäjälle. Jos arvoa ei tunnisteta tulostetaan teksti "Tuntematon arvo."
#!/bin/bash x=5 # alustetaan x 5:ksi if [ "$x" -eq 0 ]; then echo "x:n arvo on 0." elif [ "$x" -eq 5 ]; then echo "x:n arvo on 5." elif [ "$x" -eq 9 ]; then echo "x:n arvo on 9." else echo "Tunnistamaton arvo." fi
for muuttuja in lista do komentoja doneSilmukka suoritetaan listassa olevien alkioiden lukumäärää vastaava määrä kertoja. Muuttuja saa arvokseen listan sen hetkisen alkion arvon. Esimerkki tämän rakenteen käytöstä:
#!/bin/bash for piste in 1 2 3 4 5 6 7 8 9 10; do echo "." doneOhjelma tulostaa kymmenen pistettä ruudulle
#!/bin/bash for tiedosto in *; do echo "Lisätään .html loppu tiedostoon $tiedosto" mv $tiedosto $tiedosto.html sleep 1 doneTämä ohjelma lisää hakemiston kaikkien tiedostojen perään päätteen ".html". Uudelleen nimeäminen suoritetaan mv-komennon avulla.
while [ lauseke ] do komentoja doneEsimerkki while rakenteen käytöstä:
#!/bin/bash x=0; # annetaan x:lle arvo 0 while [ "$x" -le 10 ]; do echo "x:n arvo on $x" # kasvatetaan x:n arvoa x=$(expr $x + 1) sleep 1 doneOhjelmassa ehto "-le" testaa onko x:n arvo pienempi tai yhtäsuuri kuin 10. Ohjelma tulostaa x:n arvon ja kasvattaa sitä yhdellä. Tämä suoritetaan kunnes ehto on epätosi.
until [ lauseke ] do komentoja doneUntil-rakenne muistuttaa paljon while-rakennetta. Sitä suoritetaan niin kauan kun ehdon arvo on tosi.
#!/bin/bash x=0; until [ "$x" -ge 10 ]; then echo "x:n arvo on $x" x=$(expr $x + 1) sleep 1 doneSilmukkaa suoritetaan, kunnes x arvo on suurempi tai yhtä suuri kuin 10. Kun x:n arvo on 10 ja vertailu suoritetaan, vertailulohkon sisällä olevia komentoja ei enää suoriteta, joten viimeisin x:n arvo joka tulostetaan on 9.
Tässä on käsitelty AND:in ja OR:in käyttöä kontrollirakenteissa.
#!/bin/bash x=5 y=10 if [ "$x" -eq 5 ] && [ "$y" -eq 10 ]; then echo "Molemmat ehdot saivat arvon true." else echo "Ainakin toinen ehdoista saa arvon false." fiAND:in käyttö voidaan korvata seuraavan esimerkin mukaisesti kahdella if-lauseella:
#!/bin/bash x=5 y=10 if [ "$x" -eq 5 ]; then if [ "$y" -eq 10 ]; then echo "Molemmat ehdot saivat arvon true." else echo "Toinen ehdoista sai arvon false." fiAND:in käyttö on kuitenkin selkeämpää ja näin ollen suositeltavampaa, kuin kahden prättäkkäisen if-lauseen käyttö.
#!/bin/bash x=5 y=10 if [ "$x" -eq 5 ] || [ "$y" -eq 20 ]; then echo "Ainakin toinen ehto saa arvon true." else echo "Kumpikaan ehdo ei ole true." fi
#!/bin/bash # ohjelma tulostaa ensimmäisen argumentin # tutkitaan annettiinko ohjelmalle parametriä: if [ "$#" -ne 1 ]; then echo "Virhe!" fi echo "Argumentti on $1"Eli ohjelmalle pitää antaa vain yksi argumetti, eikä yhtään enempää eikä vähempää. Jos toimitaan virheellisesti ohjelma tulostaa tekstin "Virhe!". Muuten ohjelma tulostaa tekstin "Argumentti on testi", missä sana testi on juuri kyseinen ohjelmalle annettu argumentti.
luku1 -eq luku2 paluuarvo on true, jos luku1 == luku2 luku1 -ge luku2 paluuarvo on true, jos luku1 >= luku2 luku1 -gt luku2 paluuarvo on true, jos luku1 > luku2 luku1 -le luku2 paluuarvo on true, jos luku1 <= luku2 luku1 -lt luku2 paluuarvo on true, jos luku1 < luku2 luku1 -ne luku2 paluuarvo on true, jos luku1 != luku2
merkkijono1 = merkkijono2 paluuarvo on true, jos merkkijono1 = merkkijono2 merkkijono1 != merkkijono2 paluuarvo on true, jos merkkijono1 != merkkijono2 merkkijono paluuarvo on true, jos merkkijono ei ole null -n merkkijono paluuarvo on true, jos merkkijonon pituus on suurempi kuin nolla -z merkkijono paluuarvo on true, jos merkkijonon pituus on nolla
-d tiedosto paluuarvo on true, jos tiedosto on hakemisto -f tiedosto paluuarvo on true, jos tiedosto on normaali tiedosto -e tiedosto paluuarvo on true, jos tiedosto on olemassa -r tiedosto paluuarvo on true, jos tiedostoon on lukuoikeus -w tiedosto paluuarvo on true, jos tiedostoon on kirjoitusoikeus -x tiedosto paluuarvo on true, jos tiedostoon on suoritusoikeus -s tiedosto paluuarvo on true, jos tiedoston koko ei ole nolla -g tiedosto paluuarvo on true, jos tiedostolla on SGID oikeudet -u tiedosto paluuarvo on true, jos tiedostolla on SUID oikeudet
!lauseke paluuarvo on true, jos lauseke ei ole true lauseke1 -a lauseke2 paluuarvo on true, jos sekä lauseke1 että lauseke2 saavat arvon true (and) lauseke1 -o lauseke2 paluuarvo on true, jos joko lauseke1 tai lauseke2 saa arvon true (or)
#!/bin/bash x=10 # alustetaan x:n arvoksi 10 y=5 # alustetaan y:n arvoksi 5 summa=$(($x + $y)) erotus=$(($x - $y)) tulo=$(($x * $y)) jako=$(($x / $y)) jakojaannos=$(($x % $y)) # tulostetaan vastaukset: echo "Summa: $summa" echo "Erotus: $erotus" echo "Tulo: $tulo" echo "Jako: $jako" echo "Jakojäännös: $jakojaannos"Esimerkissä käytetyn syntaksin sijaan ( summa=$(($x + $y)) ) olisi voitu käyttää myös vaihtoehtoisesti jompaa kumpaa näistä:
summa=$(expr $x + $y)tai
summa=`expr $x + $y`
trap toimenpide signaaliMääritelty toimenpide suoritetaan kun signaali aktivoidaan. Esimerkki tämän käytöstä:
#!/bin/bash
# harjoitellaan trap käskyn käyttöä
# CTRL-C painaminen johtaa toimenpide()-funkition suoritukseen
trap toimenpide INT
# funktio toimenpide() tulostaa viestin
toimenpide()
{
echo "Eipä lopeteta vielä."
sleep 3
}
# suoritetaan silmukka kymmenen kertaa
for i in 1 2 3 4 5 6 7 8 9 10;
do
echo "$i"
sleep 1
done
Jos ohjelman suorituksen aikana painetaan CTRL-C näppäimiä aiheutuu keskeytys, joka johtaa toimenpide( )-funktion
suoritukseen. Näin käyttäjän painama CTRL-C saadaan käsiteltyä hallitummin. Trap voidaan määritellä myös toisella tavalla.
Tässä pari esimerkkiä:# suoritetaan oletuksena oleva toimenpide eli lopetetaan # skriptin suoritus CTRL-C-näppäimiä painettaessa trap - INT # CTRL-C ei vaikuta mitenkään ohjelman toimintaan, # vaan suoritusta jatketaan normaalisti trap '' INT
#!/bin/bash if [ -f "/etc/passwd" ]; then echo "/etc/passwd tiedosto on olemassa." exit 0 else echo "/etc/passwd tiedostoa ei ole olemassa." exit 1 fiTässä esimerkissä määriteltiin itse paluuarvot. Myös funktiot voivat palauttaa paluuarvon. Se saadaan aikaiseksi return komennolla. return:in parametriksi annetaan yksi numero argumentiksi. Muuten sen toimii exit:in tapaan, paitsi että sitä käytetään funktioiden kanssa. Esimerkki tästä:
#!/bin/bash
tutki_passwd()
{
# katsoo onko /etc/passwd olemassa:
if [ -f "/etc/passwd" ];
then
echo "/etc/passwd tiedosto on olemassa."
# löytyy, paluuarvoksi 0
return 0
else
echo "/etc/passwd tiedostoa ei ole olemassa."
# ei löydy, paluuarvoksi 1
return 1
fi
}
# otetaan tutki_passwd()-funktion paluuarvo talteen
tutki=tutki_passwd
# tarkistetaan lötykö tiedostoa:
if [ "$tutki" -eq 0 ];
then
echo "Tiedosto löytyi."
exit 0
else
echo "Tiedostoa ei löytynyt."
exit 1
fi
Ohjelmassa funktion tutki_passwd( ) paluuarvo laitetaan tutki-muuttujan arvoksi. Jos tiedosto löytyy paluuarvo on 0, jos
sitä ei löydy paluuarvoksi tulee 1. Lopuksi käyttäjälle tulostetaan tieto tiedoston löytymisestä ja poistutaan 0:lla jos se
löytyi, ja 1:llä jos ei löytynyt.bc laskin ohjelma cat tiedoston sisällön tulostus cd hakemistosta toiseen siityminen tapahtuu tämän käskyn avulla chmod muuttaa tiedoston oikeuksia echo tulostaa tekstiä näytölle grep tulostaa ehdot täyttävät rivit syötteestä less tulostaa tekstiä sivu kerrallaan ls tulostaa hakemiston sisällön mkdir luo hakemiston more ulostaa tekstiä rivi kerralaan rm poistaa tiedostoja rmdir tuhoaa hakemiston sed ohjelma tekstin muokkaamiseen sleep "nukutaan" eli ollaan tekemättä mitään määritelty aika sekunteissa sort lajittelee tiedoston rivit wc laskee rivit, sanat ja tavut tiedostosta
Bash Programming Cheat Sheet
Introduction to bash shell scripting
BASH Programming - Introduction HOW-TO
Skriptaus UNIX:eissa
Bourne-shellin ohjelmointi
Unix ja shell-ohjelmointi
Advanced Bash-Scripting Guide
Bash Reference Manual
The GNU Bourne-Again SHell
Bash man page
Bash Prompt HOWTO
Pieni Linux-kirjanen
Bourne/Bash Shell: Programmin Introduction