4.9  Ohjausrakenteet

Sh tarjoaa useimmista proseduraalisista ohjelmointikielistä tutut silmukka- ja ehtokäskyt, mutta omine erikoispiirteineen. Huomaa erityisesti testien luonne ja uudelleensuuntauksen käyttötapa.

4.9.1  If

if komentolista
then komentolista
[ elif komentolista ]
...
[ else komentolista ]
fi

Testattava ehto on mielivaltainen lista komentorivejä ja then-osa suoritetaan jos listan exit status on nolla (viimeinen komento onnistui).

Esimerkki:

if cd "$DIR" ;then
  ...
else
   echo "Hakemisto $DIR puuttuu tai on rikki" >&2
   exit 1
fi

Yleisin testattava komento on test, josta enemmän alla.

4.9.2  While

while komentolista
do komentolista
...
done

Silmukan käskyjä toistetaan niin kauan kuin ehto on tosi, ts. niin kauan kuin ko. komentolistan status on nolla.

Esimerkkejä:

Tulostetaan kaksi tiedostoa rinnakkain:

while 
  read sana1 &&
  read sana2 <&3 
do
  printf "%s\t%s\n" "$sana1" "$sana2"
done <file1 3<file2 

Etsitään /etc/passwd:stä käyttäjiä, joiden uid on nolla:

while IFS=: read id pw uid junk
do case "$id:$uid" in
  root:0) :	;;
  *:0)	  echo "Found an extra root: $id!!" ;;
  esac
done </etc/passwd

Uudelleensuuntaus on ohjausrakenteissa laitettava rakenteen loppusanan jälkeen, jolloin se vaikuttaa koko rakenteeseen. (Huom. jotkin shellit suorittavat tässä tilanteessa koko rakenteen alishellinä. Jos tällöin esim. uudelleensuunnatussa silmukassa halutaan asettaa muuttujia, täytyy uudelleensuuntaus tehdä execillä.)

4.9.3  Until

until komentolista
do komentolista
...
done

Silmukan käskyjä toistetaan niin kauan kuin komentolista palauttaa nollasta eroavan statuksen, ts. olennaisesti kuin while !... Harvoin käytetty, yleensä while on havainnollisempi.

4.9.4  For

For-lause eroaa syntaksiltaan ja käytöltään jo enemmän ``perinteisistä'' ohjelmointikielistä:

for name [in lista]
do
...
done

Silmukka toistetaan muuttujan name saadessa kaikki listan arvot. (Jos lista puuttuu, oletus on "$@".)

4.9.5  Break, continue

While-, until- ja for- silmukoista voi myös poistua kesken komennolla break. Jos silmukoita on useita sisäkkäin, breakille voi antaa kokonaislukuargumentin joka kertoo monestako sisimmästä hypätään ulos.

Vastaavasti continue [n] hyppää silmukan alkuun.

4.9.6  : ja !

Kaksoispiste on ns. tyhjä komento (null command): se ei tee mitään (mutta sen argumentit puretaan ja niiden mahdolliset sivuvaikutukset suoritetaan).

Varattu sana ! komento suorittaa komennon ja palauttaa statuksen 1 jos sen sen status oli 0, muuten 1. Sitä voi käyttää etenkin if-, while- ja until-lauseissa ehdon merkityksen kääntämiseen.

Huutomerkki shellin toimintona on suhteellisen uusi lisäys eikä sitä kaikista shelleistä vieläkään löydy (ei edes ksh88:sta), niinpä usein näkee tällaista:

if prog1 ;then : ;else 
  ...
fi
mikä on sama kuin
if ! prog1 ;then
 ...
fi

4.9.7  Case

Hiukan erikoisempi on case-lause:

case merkkijono in
 pattern1) komentolista1 ;;
 pattern2) komentolista2 ;;
  ...
esac

Toisin kuin if, case tekee testin itse: se vertaa annettua merkkijonoa järjestyksessä annettuihin merkkijonolausekkeisiin, löydettyään täsmäävän se suorittaa sitä seuraavat komennot kaksoispuolipisteeseen saakka ja jatkaa suoritusta esacin jäljestä.

Viimeisen ;;-parin voi jättää pois, mutta mitään tapaa jatkaa suoritusta seuraavan vaihtoehdon komennoista ei ole. (Ksh tarjoaa tähän dokumentoimattoman (?) keinon: ;;:n paikalle ;&. Sen käyttöä lienee kuitenkin syytä välttää.)

Huomaa erikoinen parittomien sulkujen käyttö. POSIX edellyttää kyllä, että niille saa kirjoittaa parinkin lausekkeen vasemmalle puolelle, mutta yleensä sitä ei tehdä.

Merkkijonolausekkeissa käytettävä syntaksi on sama kuin tiedostonimilaajennuksissa edellä (mutta ilman kauttaviivaa ja pistettä koskevia rajoituksia).

Esimerkki: Olkoon tiedostossa koneet koneiden nimiä ja tyyppejä tähän tapaan:

tarzan	HP-UX 11.0
jane	HP-UX 10.20
josef	Solaris 5.7
korak	HP-UX 10.20
volsung	Linux RedHat 7.0
Halutaan tutkia koneiden tyypit suorittamalla niissä ssh:lla (Secure Shell; oletetaan että se on konfiguroitu niin että 'ssh kone komento' toimii ko. koneille ilman salasanaa) eri käyttöjärjestelmissä sopiva komento:
while read hostname type junk
do
  case "$type" in
    HP*)  		ssh $hostname model ;;
    Solaris|Sun*)	ssh $hostname uname -p ;;
    Linux)		ssh $hostname uname -m ;;
  esac </dev/null
done <koneet

4.9.8  test, [

Komentoa test käytetään yleensä if-, while- ja until-rakenteissa (sekä ||:n ja &&:n kanssa) ehtotestinä. Sillä on kaksi syntaksia, riippuen siitä kutsutaanko sitä nimellä test vain [:

test ehtolauseke

[ ehtolauseke ]

Vaikutus on sama, ainoa ero on että [ vaatii loppuun ]:n, ja jos sitä halutaan käyttää ulkoisena tarvitaan test (esim. find ... -exec test ...). Huomaa että hakasulut on erotettava lausekkeesta tyhjällä.

Ehtolausekkeessa voi olla seuraavanlaisia testejä:

-b tiedosto : tosi jos tiedosto ja blokkilaitetiedosto (block special file)
-c tiedosto : tosi jos tiedosto ja merkkilaitetiedosto (character special file)
-d tiedosto : tosi jos tiedosto on hakemisto
-e tiedosto : tosi jos tiedosto on olemassa
-f tiedosto : tosi jos tiedosto on normaali tiedosto (BSD: on olemassa mutta ei ole hakemisto)
-g tiedosto : tosi jos tiedoston setgid-bitti on päällä
-n merkkijono : tosi jos merkkijono on epätyhjä
-p tiedosto : tosi jos tiedosto on nimetty putki (FIFO)
-r tiedosto : tosi jos tiedosto on luettavissa
-s tiedosto : tosi jos tiedoston koko on suurempi kuin nolla
-t tiedostokahva : tosi jos on auki ja päätelaite
-u tiedosto : tosi jos tiedoston setuid-bitti on päällä
-w tiedosto : tosi jos tiedosto on kirjoitettavissa (w-bitti päällä - read-only tiedostojärjestelmässä tämä voi olla tosi vaikkei tiedostoon voikaan kirjoittaa)
-x tiedosto : tosi jos tiedosto on suoritettavissa (x-bitti päällä, myös hakemistoille)
-z merkkijono : tosi jos merkkijono on tyhjä
merkkijono : tosi jos merkkijono ei ole tyhjä (vaarallinen jos merkkijono voi alkaa - :lla!)
s1 = s2 : tosi jos merkkijonot s1 ja s2 ovat identtisiä
s1 ! = s2 : tosi jos merkkijonot s1 ja s2 ovat erilaisia
n1 -eq n2 : tosi jos kokonaisluvut n1 ja n2 ovat yhtäsuuria
n1 -ne n2 : tosi jos kokonaisluvut n1 ja n2 ovat erisuuria
n1 -gt n2 : tosi jos kokonaisluku n1 on suurempi kuin n2
n1 -ge n2 : tosi jos kokonaisluku n1 on suurempi tai yhtäsuuri kuin n2
n1 -lt n2 : tosi jos kokonaisluku n1 on pienempi kuin n2
n1 -le n2 : tosi jos kokonaisluku n1 on pienempi tai yhtäsuuri kuin n2

Kaikki tiedostotestit palauttavat epätoden jos tiedostoa ei ole olemassa.

Kaikkien edellä lueteltujen merkityksen voi kääntää negaatiokseen lisäämällä eteen huutomerkin (vrt. huutomerkkiin sh:n varattuna sanana).

POSIX.2 ei edellytä mitään muuta, erityisesti ei sulkuja eikä muutakaan testien yhdistelytapaa. Sekä sulut että seuraavat toimivat kuitenkin sekä BSD:ssä että SysV:ssä (ja jokseenkin kaikkialla) mutta joissakin tilanteissa yhteensopimattomilla tavoilla:

expr1 -a expr2 : tosi jos molemmat lausekkeet ovat tosia
expr1 -o expr2 : tosi jos jompikumpi lauseke on tosi

Näiden prioriteetti on alhaisempi kuin edellämainittujen (myös !:n) ja -o:n alhaisempi kuin -a:n. Järjestystä voi muuttaa suluilla normaaliin tapaan (ne ovat sh:n erikoismerkkejä ja suojattava).

Olennainen siirrettävyysongelma näiden kanssa on se, että BSD:ssä yksiargumenttisilla optioilla on korkeampi prioriteetti kuin merkkijonovertailuilla, sysV:ssä päinvastoin. Merkkijonot voivat muutenkin aiheuttaa yllätyksiä monimutkaisissa lausekkeissa, jos ne saattavat alkaa erikoismerkeillä - ! = ( ) (sysV:ssä jopa test -n "$x" aiheuttaa virheen jos $x on '='). Tämän takia merkkijonojen vertailukin usein tehdään tyyliin test X"$a" = X"$b", vaikka POSIX-säännöillä moista ei tarvitakaan. Sen sijaan test "$x" voi aiheuttaa virheen POSIX-yhteensopivassakin shellissä (jos $x on esim.  "-f").

Siirrettävyysongelmien takia -a:ta ja -o:ta on syytä välttää vähänkään monimutkaisemmissa lausekkeissa. Esim. test expr1 -o expr2 on korvattavissa siirrettävällä muodolla test expr1 || test expr2 (-a samaan tapaan &&:lla). Huomaa kuitenkin että test-komennossa -a:lla on korkeampi prioriteetti kuin -o:lla mutta shellin || ja && ovat samanarvoisia.

4.9.9  Ksh: [[...]]

Korn shellissä on testin lisäksi sisäänrakennettuna testisyntaksi [[ ehtolauseke ]] . Hakasulkuparit ovat varattuja sanoja ja vaativat ympärilleen välilyönnit tms. Ehtolauseke on samanlainen kuin test-komennossa, -b, -c, -d, -f, -g, -n, -p, -r, -s, -t, -u, -w, -x, -z -eq, -ne, -lt, -gt, -le ja -ge toimivat samalla tavoin, ja sen lisäksi:

-a tiedosto : tosi jos tiedosto on olemassa (sama kuin -e - vanhemmissa ksh:ssa -e puuttuu)
-h tiedosto : tosi jos tiedosto on symbolinen linkki
-k tiedosto : tosi jos tiedoston sticky bit on päällä
-o optio : tosi jos optio on päällä
-L tiedosto : tosi jos tiedosto on symbolinen linkki (sama kuin -h)
-O tiedosto : tosi jos tiedosto on oma (omistaja shell-prosessin effective UID)
-G tiedosto : tosi jos tiedosto on omassa ryhmässä (ryhmä shell-prosessin effective GID)
-S tiedosto : tosi jos tiedosto on socket
tiedosto1 -nt tiedosto2 : tosi jos tiedosto1 on uudempi kuin tiedosto2
tiedosto1 -ot tiedosto2 : tosi jos tiedosto1 on vanhempi kuin tiedosto2
tiedosto1 -ef tiedosto2 : tosi jos tiedosto1 ja tiedosto2 ovat sama tiedosto
string = pattern : tosi jos merkkijono string vastaa mallia pattern
string ! = pattern : tosi jos merkkijono string ei vastaa mallia pattern
s1 < s2 : tosi jos merkkijono s1 on pienempi kuin s2 (aakkosjärjestyksessä)
s1 > s2 : tosi jos merkkijono s1 on suurempi kuin s2

Lisäksi ehtoja voi yhdistellä seuraavilla, prioriteettijärjestyksessä:

  1. sulut ()
  2. ! (negaatio)
  3. && JA
  4. || TAI

Tuplahakasulkujen sisällä ei levitetä jokerimerkkejä eikä erotella sanoja IFS:llä, ja ehtojen yhdistelysäännöt on määritelty täsmällisesti joten niitä voi käyttää siirrettävästi (shelleissä joissa [[ ]] on). Huomaa myös merkkijonovertailun ero testiin.

4.9.10  exit

exit n

lopettaa shellin suorituksen, palauttaen statuksena argumenttinsa (tai sen puuttuessa nollan).

4.9.11  exec

exec komento [argumentteja...]

suorittaa nimetyn komennon niin, että se korvaa käynnissä olevan shellin luomatta uutta prosessia (vrt. execv(3)). (Huom. se ei siitä huolimatta peri shellin sisäisiä muuttujia, optioita jne.)

Komentorivillä mahdollisesti annetut syötön tai tulostuksen uudelleensuuntaukset tehdään ensin. Jos exec suoritetaan ilman komentoa, se ei mitään muuta teekään kun suuntaa shellin I/O:n uudelleen.

Esimerkki: Alkuperäisessä Bourne sh:ssa ohjausrakenteen I/O:n uudelleensuuntaus aiheutti sen suorittamisen alishellissä, ja jotkin sh:t tekevät niin yhä. Niinpä haluttaessa asettaa muuttuja uudelleensuunnatussa silmukassa tarvittiin tällaista:

exec 3<&0
exec <file
while read key value comment ;do
  case "$key" in
    SECRET)  x=$value ;;
  esac
done
exec 0<&3

4.9.12  eval

Komento eval suorittaa merkkijonon sisältämän komennon. Esim.

x='y=5'
eval $x
echo $y

Eval edellyttää erityistä huolellisuutta erikoismerkkien suojauksen kanssa, ne kun tulevat käsitellyksi kahteen kertaan.

Esim. simuloidaan taulukkoa:

tau() {
# tau taulukko indeksi [value]
# asettaa tai palauttaa taulukon alkion
  case $# in
     2)  eval 'printf "%s\n" "$'"$1_$2"\" ;;
     3)  eval "$1_$2=\"\$3\"" ;;
	 # eval "$1_$2='$3'" ei toimi - miksei?
     *)  printf "usage: tau arrayname index [value]\n" 
	 return 1 ;;
  esac
}

Esim. kuvitellaan esihistoriallinen shell jossa ei ole eval-komentoa. Sitä voisi silloin matkia tähän tapaan:

myeval() {
  printf "%s\n" "$*" > /tmp/.myeval_temp_file_$$
  . /tmp/.myeval_temp_file_$$
  rm /tmp/.myeval_temp_file_$$
}

4.10  Aritmetiikka

4.10.1  Expr

expr lauseke

laskee arvon lausekkeelle, joka koostuu erillisinä argumentteina annetuista kokonaisluvuista ja merkkijonoista ja seuraavista operaatioista:

( ) | & = > >= < <= != + - * / % :

Huomaa että monet noista ovat shellin erikoismerkkejä jotka on suojattava.

Laskutoimitukset + - * / % on määritelty kuten C:ssä mutta vain kokonaisluvuille.

Vertailut > >= < <= != toimivat sekä kokonaislukujen että merkkijonojen kanssa (jälkimmäisillä aakkosjärjestysvertailu, kieliriippuvainen): tulos on 0 (epätosi) tai 1 (tosi).

Erikoisempia ovat seuraavat:

| TAI: expr1 | expr2 palauttaa ensimmäisen lausekkeen jos se ei ole nolla eikä tyhjä, muuten jälkimmäisen.
& JA: expr1 & expr2 palauttaa ensimmäisen lausekkeen jos kumpikaan lausekkeista ei ole nolla eikä tyhjä, muuten nollan.
: merkkijonovertailu: string : regexp vertaa merkkijonoa malliin (merkkijonolauseke, syntaksi kuten grepissä mutta implisiittisesti ankkuroitu alusta (mutta ei lopusta), ^ lausekkeen alussa ei ole tarpeen (se saattaa toimia tai sitten ei, POSIX.2 jättää asian auki). Tulos on nolla jos vastinetta ei löydy, muuten täsmäävien merkkien lukumäärä tai jos mallissa on ryhmittelysulkeita \(...\), ensimmäisen sisältöä vastaava merkkijono.

Sulkuja voi käyttää ryhmittelyyn tavalliseen tapaan, muuten prioriteettijärjestys on seuraava:

  1. :
  2. * / %
  3. + -
  4. = > >= < <= !=
  5. &
  6. |

Exit status on 0 jos tulos oli jotain muuta kuin nolla tai tyhjä, mikä helpottaa exprin käyttöä testeissä.

Argumenttien erillisyysvaatimus, useiden shellin erikoismerkkien suojaustarve sekä merkkijonojen sekoittuminen operaattoreihin tekee expristä hankalan käyttää, eikä sitä nykyisin juurikaan tarvita laskutoimituksiin ellei haluta yhteensopivuutta vanhojen shellien kanssa. Ainoastaan sen merkkijonovertailuoperaattori on selvästi voimakkaampi kuin shellin muuttujankäsittelytoiminnot ja usein kätevämpi kuin esim. sed.

4.10.2  $((...))

POSIX shellit osaavat sisäisesti laskea aritmeettisia lausekkeita tällaisella syntaksilla:

$$((lauseke ))

Lauseke puretaan samassa vaiheessa kuin muutkin $-alkuiset (muuttujat jne), ja sen sisällä olevat erikoismerkit suojataan kuten tuplalainausmerkkien sisällä paitsi että '' itse ei ole siellä erikoismerkki. Lausekkeen sisällä muuttujien arvoihin viitataan ilman dollarimerkkiä. Sallittuja laskutoimituksia ovat C:n tuntemat kokonaislukuoperaatiot (poislukien ++, -- ja sizeof), samoilla prioriteettisäännöillä:

  1. + ja - etumerkkeinä, ! (negaatio)
  2. *, / : kerto- ja jakolasku
  3. + ja - laskutoimituksina
  4. <<, >> : bittisiirrot
  5. <, <=, >, >= : erisuuruusvertailut
  6. =, != : yhtäsuuruusvertailut
  7. & : bitti-JA
  8. ^ : bitti-XOR
  9. | : bitti-TAI
  10. && : looginen JA
  11. || : looginen TAI
  12. expr ? expr : expr : ehtolauseke
  13. =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= : sijoitukset

Huom. Kyseessä ei ole komento, haluttaessa laskea lauseke käyttämättä sen tulosta täytyy käyttää apuna esim. null-komentoa (kaksoispiste), seuraavat tekevät saman asian:

: $((a+=2))
a=$((a+2))
Sitä ei myöskään suoraan voi käyttää ehtotestissä (case:ssa kylläkin), vaan tarvitaan jotain tällaista:
if [ $((x>y)) -ne 0 ] 
then ...

4.10.3  Ksh: ((...))

Ksh tuntee myös komentomuotoisen aritmetiikan kahdella syntaksilla:

((lauseke))
let lauseke ...

Erona noilla on että edellinen suojaa sulkujen sisällön automaattisesti samoin kuin tuplalainausmerkit (ts. ((...)) on sama kuin let "..."), ja letille voi antaa monta lauseketta.

Kummassakin tapauksessa exit status on nolla jos (viimeisen) lausekkeen arvo on nollasta eroava, muussa tapauksessa yksi. Tätä voi siis käyttää suoraan esim. if-lauseessa.

Ksh tulkitsee tuplasulut operaattorimerkeiksi joten niitä ei tarvitse erottaa välilyönnein (mistä toisaalta seuraa tarve erottaa kaksi peräkkäistä alishelliä merkitsevää sulkua toisistaan).

Seuraava: 4.11-4.12 Shellin optiot, trap
Edellinen: 4.7-4.8 Komentorivin tulkinnasta


File translated from TEX by TTH, version 1.98.
On 17 May 2001, 18:14.