Sh tarjoaa useimmista proseduraalisista ohjelmointikielistä tutut silmukka- ja ehtokäskyt, mutta omine erikoispiirteineen. Huomaa erityisesti testien luonne ja uudelleensuuntauksen käyttötapa.
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.
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ä.)
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.
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 "$@".)
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.
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 ... fimikä on sama kuin
if ! prog1 ;then ... fi
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.0Halutaan 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
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ä:
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:
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.
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:
Lisäksi ehtoja voi yhdistellä seuraavilla, prioriteettijärjestyksessä:
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.
exit n
lopettaa shellin suorituksen, palauttaen statuksena argumenttinsa (tai sen puuttuessa nollan).
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
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_$$ }
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:
Sulkuja voi käyttää ryhmittelyyn tavalliseen tapaan, muuten prioriteettijärjestys on seuraava:
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.
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ä:
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 ...
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