POSIX shell tutoriaali

ITKA203 Käyttöjärjestelmät -kurssin materiaalia. Oli tarkoitus "vähän suomentaa POSIXia demoon 6", mutta loppujen lopuksi näyttää siltä, että tarpeettoman paljon tässä olisi materiaalia siihen nähden, mitä demossa 6 oikeastaan tarvittiin.

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

Jyväskylän yliopiston tietotekniikan laitos.

STATUS: TÄMÄ OSUUS TULEVAISUUTTA VARTEN; ei vielä 2015 Ihan kiva ja hyödyllinen tästä vois tulla, mutta pittää mitoittaa jotenkin uudelleen tulevilla kurssikerroilla. Kevään 2015 kurssi pärjännee nyt esimerkin läpivalaisulla..

Contents

Tämän harjoituksen tavoitteet

Harjoituksen tavoitteita:

Keväällä 2015 määriteltyjen osaamistavoitteiden osalta tämän ja aiemmat demot tehtyään opiskelija:

Mitä shell tekee

Hieman tulkiten, ja kurssin mukaisin toteutuslaajennoksin varustettuna, POSIXin mukaan shell suorittaa seuraavat toimenpiteet:

Kohtalaisen yksinkertaisesta välineestä on lähtökohtaisesti kyse. Voima on apuohjelmissa, joita komennoilla suoritetaan, sekä muutamissa sisäänrakennetuissa ohjelmointiominaisuuksissa.

Syntaksi

Erikoismerkit

Shell on tarkka syntaksista, mm. koska pilkkominen tapahtuu välilyöntien ja tiettyjen erikoismerkkien kohdalta. Erityisesti seuraavien merkkien osalta on oltava varuillaan aina:

| & ; < > ( ) $ ` \ " ' <välilyönti> <tabulaattori> <rivinvaihto>

Seuraavien merkkien osalta joissain tapauksissa:

* ? [ # ~ = %

Mikäli haluaa edellä mainitun merkin osaksi jotakin esimerkiksi komennon argumentiksi tarkoitettua merkkijonoa, on kolme vaihtoehtoa:

  • Normaali heittomerkkilainaus (single quotes) 'erikoismerkkejä ~"\) ja muuta' säilyttää sisällä olevien merkkien alkuperäisen merkityksen, mutta heittomerkkiä ei voi sen sisällä olla.

  • Tuplalainausmerkkien (double quotes) "joitain erikoismerkkejä kuten ~<>;' ja välilyöntejä" sisällä osa erikoismerkeistä, kuten $ ja \, tulkitaan kuten ne tulkittaisiin lainausmerkkien ulkopuolellakin! (Totuus lainausmerkkisäännöistä löytyy standardista ja käyttämäsi shellin manuualista.)

  • Yksittäisen merkin todellisen sisällön saa mukaan myös "pakenemalla hetkeksi" normaalista tulkinnasta pakomerkillä (escape character), missä roolissa shellissä toimii kenoviiva \ -- näin ollen yllä luetellut merkit voitaisiin kirjoittaa omassa merkityksessään seuraavilla kombinaatioilla:

    \| \& \; \< \> \( \) \$ \` \\ \" \' \<välilyönti> \<tabulaattori>
    \* \? \[ \# \~ \= \%
    

Pakomerkkikäytäntö on tyypillinen kaikissa ohjelmointikielissä ja ympäristöissä, joissa joillain merkeillä on erikoisrooli, mutta halutaan sallia merkkien kirjoittaminen myös omana itsenään. Jos haluaa esimerkiksi lainausmerkin lainausmerkkien sisään, voi kirjoittaa esim:

echo "Ei ole \"rakettitiedettä\" tämäkään"

Rivinvaihtoa ei ole mahdollista kirjoittaa pakomerkin avulla, koska sille on sovittu vielä eri rooli, eli rivinvaihdon poistaminen syötejonosta. Mitä tämä tarkoittaa? Yhdistelmä \<rivinvaihto> shell-skriptissä näyttäytyy ensinnäkin esimerkiksi tekstieditorissa sillä tavoin, että rivin viimeinen merkki on kenoviiva:

echo apina \
papina \
pam

Shell-syntaksissa on sovittu, että tällainen syntaksi tarkoittaa rivin jatkumista ikään kuin rivinvaihtoa ei olisi ollutkaan. Näin voidaan kirjoittaa nätisti rivitettynä hyvinkin pitkiä komentoja ilman todellista rivinvaihtoa. Edellinen siis muuttuisi ennen jatkotulkintaa seuraavaksi yksittäiseksi riviksi:

echo apina papina pam

Tämä toimii vain, kun yhdistelmä todella on \<rivinvaihto> eli tätä käyttäessä on varmistettava, ettei vahingossa ole välilyöntejä kenoviivan perässä. (Jos olisi, niin se tulkittaisiin kirjaimellisesti välilyöntinä)

POSIX määrittelee, että komentorivin täytyy voida olla mielivaltaisen mittainen - vaikka satoja tai tuhansia merkkejä. Kenoviivajatko helpottaa sellaisten kirjoittamista kauniisti rivittäen. Esimerkiksi muutaman sadan merkin mittaiset argumenttilistat ovat aivan tyypillisiä joidenkin apuohjelmien käytössä.

Huomautus lainausmerkkien käytöstä

Lainausmerkkejä kannattaa käytellä runsaasti, koska esimerkiksi tiedostonimissä tai käyttäjän antamissa argumenteissa voi olla välilyöntejä tai muita shellin erikoismerkkejä mukana. Esimerkki, josta tämä käy ilmi:

[nieminen@localhost esimerkki]$ ls -l
total 0
-rw-rw-r--. 1 nieminen nieminen 0 May 20 19:44 tiedostoA
-rw-rw-r--. 1 nieminen nieminen 0 May 20 19:44 tiedostoA tiedostoB
-rw-rw-r--. 1 nieminen nieminen 0 May 20 19:44 tiedostoB
[nieminen@localhost esimerkki]$ muuttuja="tiedostoA tiedostoB"
[nieminen@localhost esimerkki]$ ls -l $muuttuja
-rw-rw-r--. 1 nieminen nieminen 0 May 20 19:44 tiedostoA
-rw-rw-r--. 1 nieminen nieminen 0 May 20 19:44 tiedostoB
[nieminen@localhost esimerkki]$ ls -l "$muuttuja"
-rw-rw-r--. 1 nieminen nieminen 0 May 20 19:44 tiedostoA tiedostoB
[nieminen@localhost esimerkki]$ ls -l '$muuttuja'
ls: cannot access $muuttuja: No such file or directory
[nieminen@localhost esimerkki]$

Muuttujien syntaksista puhutaan myöhemmin lisää, mutta eiköhän sekin voida jo ymmärtää... Esimerkin työhakemistossa on kolme tiedostoa, joista yhden nimeen sisältyy välilyönti. Eli on tiedostoA, tiedostoB ja tiedostoA tiedostoB. Komento muuttuja="tiedostoA tiedostoB" tekee nykyiseen shell-istuntoon muuttujan nimeltä muuttuja sisältönään merkkijono tiedostoA tiedostoB, jossa on mukana välilyönti. Lainausmerkkien sisällähän voi olla mm. välilyönti sellaisenaan. Sitten nähdään jänniä: Komento ls -l $muuttuja laventaa muuttujan sisällön sellaisenaan komentoriville ennen kuin sitä tulkitaan lisää. Erikoismerkki $ palvelee muun muassa tätä tarkoitusta eli muuttujien laventamista. Rivistä tulee muuttujan sisällön laittamisen jälkeen seuraavanlainen:

ls -l tiedostoA tiedostoB

Sitten vasta tämä rivi tulkitaan välilyönneillä eroteltuna, ja tässähän tiedostoA ja tiedostoB ovat nyt kaksi erillistä argumenttia! Näin ollen ls näyttää näiden kyseisten tiedostojen tiedot. Sen sijaan esimerkin viimeinen komento ls -l "$muuttuja" laventaa muuttujan lainausmerkkien sisään, ja jäljelle jää:

ls -l "tiedostoA tiedostoB"

Tätä alunperin tarkoitettiin! Tuplalainausmerkkien sisällä toimivat ainoastaan seuraavat erikoismerkit:

` \ $

Gravis eli "takahipsu" (backtick) on tuttu aiemmista demoista. Niiden sisään merkitty komento suoritetaan, ja komennon tuloste korvaa alkuperäisen takahipsulainauksen. Kyseessä on siis niin sanottu komentokorvaus (command substitution). Esimerkkejä tästä on nähty jo aiemmissa demoissa... esimerkiksi:

echo "Käyttäjä `whoami` ajoi tämän hetkellä `date`"

Shell suorittaa ensin takahipsuihin merkityt komennot, ja vasta sen jälkeen lopullinen komentorivi suoritetaan. Edellisestä tulisi sitten lopulta suoritettavaksi esimerkiksi seuraava komentorivi:

echo "Käyttäjä nieminen ajoi tämän hetkellä Wed May 20 20:58:52 EEST 2015"

Kenoviiva \ tosiaan toimii lainausmerkkien sisällä, esim:

echo "Voin tulostaa dollarimerkin kyllä, tällä tavoin: \$"

Dollarimerkki $ tekee muuttujien sisällön laventamisen lisäksi muutakin, mihin palataan myöhemmin, myös tuplalainausmerkkien sisällä. Mainitaan ennakoivasti esimerkiksi, että äskeinen esimerkki olisi voinut olla kirjoitettu myös seuraavalla tavalla:

echo "Käyttäjä $(whoami) ajoi tämän hetkellä $(date)"

Yksittäiset lainausmerkit olisivat edellisen tiedostoesimerkin tarkoitukseen liian tiukat:

[nieminen@localhost esimerkki]$ ls -l '$muuttuja'
ls: cannot access $muuttuja: No such file or directory
[nieminen@localhost esimerkki]$

Tässä nimittäin komento ls yrittäisi listata sellaisen tiedoston tietoja, jonka nimi olisi aivan konkreettisesti $muuttuja, koska yksittäisten lainausmerkkien sisällä ei tapahdu muuttujan sisällön laventamista, eikä muutakaan erikoismerkeillä hoidettavaa operointia.

Tämän esimerkin valossa kerrataan vielä: Shell on tarkka syntaksista; kaikki merkit vaikuttavat, ja niiden merkitykset pitää tietää tai tarkistaa käyttöohjeista, kun ne kumminkin aina unohtuvat; lainausmerkkejä kannattaa käytellä runsaasti, koska siihen on aika hyvä syy.

Komentorivien jakaminen osiinsa

Shell tulkitsee syötejonoaan, eli esimerkiksi interaktiivista päätesyötettä tai skriptitiedostoa, merkki kerrallaan edeten. Se jakaa syötettä osiin välimerkkien kohdalta tiettyjen sääntöjen mukaan. Olennaista on:

  • Operaattorit koostuvat tietyistä erikoismerkeistä. Peräkkäisistä operaattorimerkeistä voi muodostua pidempi operaattori. Kurssilla alusta alkaen nähtynä esimerkkinä esimerkiksi > ja >> ovat shellin näkökulmasta eri operaattoreita.

  • Operaattori tulkitaan loppuneeksi, jos seuraava merkki ei pysty kuulumaan mihinkään operaattoriin. Esimerkiksi seuraavat tekevät saman:

    cat kissa>koira
    cat kissa >koira
    cat kissa > koira
    

    Selkeintä voipi olla tässä tapauksessa kirjoittaa välilyönnit, vaikka tietäisi, että ne eivät ole tässä välttämättömät.

  • Lainausmerkeistä kerrottiin jo ylempänä. Niiden sisältö menee keskenään samaan köntsään. Huomionarvoista on kuitenkin, että lainauksen päättyminen ei päätä meneillään olevaa syötepalasta. Esimerkki:

    [nieminen@localhost esimerkki]$ echo "a""b"'c'"d""`date`"'e''f''"g"'
    abcdWed May 20 21:38:26 EEST 2015ef"g"
    
  • Yleisesti ottaen takahipsu ja dollarimerkki aloittavat uudenlaisen tulkinnan, eli komennon suorittamisen tai muuttujan sisällön sijoittamisen.

  • Operaattori lopettaa edellisen syötepalasen, joten seuraavassa esimerkissä tulostuu tiedostoon nimeltä ef"g":

    [nieminen@localhost esimerkki]$ echo "a""b"'c'"d""`date`">'e''f''"g"'
    [nieminen@localhost esimerkki]$ ls -l
    total 8
    -rw-rw-r--. 1 nieminen nieminen 34 May 20 21:40 ef"g"
    -rw-rw-r--. 1 nieminen nieminen  4 May 20 21:31 tiedostoA
    -rw-rw-r--. 1 nieminen nieminen  0 May 20 19:44 tiedostoA tiedostoB
    -rw-rw-r--. 1 nieminen nieminen  0 May 20 19:44 tiedostoB
    

    Edelleen, on fiksua kirjoittaa välilyönnit tarkoittamansa merkityksen selventämiseksi!

  • Välilyönti erottaa komennon osia, mutta ei kuulu mihinkään palaseen itseensä. Esimerkki:

    [nieminen@localhost esimerkki]$ echo a b
    a b
    [nieminen@localhost esimerkki]$ echo a      b
    a b
    

    Välilyöntejä kannattaakin käyttää mahdollisimman paljon oman koodin selkeyttämiseksi -- kuten ohjelmoinnissa yleensäkin tulee tehdä.

Alias -tulkinta

Hyödyllisellä komennolla alias voi määritellä uusia merkkijonoja, jotka shell korvaa komentoriville (alias substitution). Aliaksia voisi haluta käyttää esimerkiksi tosi lyhyiden komentolyhenteiden määrittelyyn:

[nieminen@localhost esimerkki]$ alias l="ls -lat"
[nieminen@localhost esimerkki]$ l
total 16
drwxrwxr-x. 2 nieminen nieminen 4096 May 20 21:40 .
-rw-rw-r--. 1 nieminen nieminen   34 May 20 21:40 ef"g"
-rw-rw-r--. 1 nieminen nieminen    4 May 20 21:31 tiedostoA
-rw-rw-r--. 1 nieminen nieminen    0 May 20 19:44 tiedostoA tiedostoB
-rw-rw-r--. 1 nieminen nieminen    0 May 20 19:44 tiedostoB
drwxrwxr-x. 4 nieminen nieminen 4096 May 20 19:42 ..

Aliakset tulkitaan vain silloin, kun ne esiintyvät komentorivillä komennon roolissa.

Varatut sanat

Shellin ohjelmointirakenteet perustuvat ns. varattuihin sanoihin (reserved words) joilla on syntaktinen ja semanttinen merkitys komentorivien ja skriptien tulkinnassa. Näitä ovat seuraavat:

!       do      esac    in
{       done    fi      then
}       elif    for     until
case    else    if      while

Osa näistä on varmasti edeltävän ohjelmointikurssin pohjalta hyvin tuttuja sanoja. Osa voi vaikuttaa jollain tapaa "hassulta". Esimerkiksi if -rakenne lopetetaan shellissä sanalla fi (joka on niinkuin "if" toisin päin) ja case -rakenne lopetetaan sanalla esac. Toisaalta do -rakenne lopetetaankin sanalla done. Kaikki tämä on kuitenkin vain syntaksia, joka vaihtelee ohjelmointikielestä toiseen.

Varattu sana on tietyssä erityisessä merkityksessään vain tietyissä paikoissa:

  • Komennon ensimmäisenä sanana (silloin ohjelmatiedoston suorittamisen sijasta shell tietää aloittaa jonkin ohjelmointirakenteen!).
  • Ensimmäinenä sanana, joka tulee välittömästi jonkin toisen varatun sanan perään (paitsi case, for, in, joiden jälkeen pitää sovitusti tulla muuta kuin varattu sana, ja rakenne on määritelty muutenkin tarkemmin).
  • näissäkin paikoissa tietysti vain silloin, kun sanan ympärillä ei ole lainausmerkkejä.

Edellä mainittujen lisäksi POSIX toteaa, että seuraavat voivat olla laajennetuissa toteutuksissa määritelty varatuiksi sanoiksi:

[[      ]]      function    select

Esimerkiksi bash tarjoaa POSIXia näppärämpiä ehtolauselaajennoksia syntaksilla [[ EHTO ]]. Jos näitä käyttää, joutuu kiinnitetyksi bashiin, ja erityisesti silloin on syytä mainita tiedoston alussa rivin #!/bin/bash muodossa, että skripti käyttää nimenomaan bashin syntaksia.

Standardin puolesta tulos näiden sanojen käytöstä voi olla mitä tahansa ("unspecified"). Tiukasti POSIX-yhteensopivissa komennoissa tai skripteissä näitä ei siis saisi käyttää.

Erikoismerkkien merkityksistä

Kommenttimerkki

Numeromerkki / risuaita / hash eli # aloittaa kommentin, mutta vain, jos se aloittaa uuden palasen. Rivinvaihtoon asti on sitten kommenttia, jota shell ei yritäkään tulkita. Esimerkki:

[nieminen@localhost esimerkki]$ echo Hei#maailma
Hei#maailma
[nieminen@localhost esimerkki]$ echo Hei #maailma
Hei

POSIX eksplikoi, että skriptin lopputulos on määrittelemätön (unspecified), mikäli ensimmäisen rivin ensimmäiset merkit ovat #!. Käytännössä suurta haittaa ei pitäisi koitua siitä, että tämän "hashbangin" perään laittaa sen shellin tiedosto-osoitteen, jolla skripti on tarkoitus ajaa. Laajennetulla toiminnalla varustettujen, esim. GNUn bash-shellin skripteissä puolestaan on syytä olla nimenomaisen shellin nimi ensimmäisellä rivillä, ettei niitä vahingossa yritetä ajaa perusmallin tulkilla. Mm. Linux-ympäristössä hashbangillä voi ilmoittaa muunkin tulkattavan kielen tulkkiohjelman. Esimerkkejä eri kielillä tehtyjen tulkattavien ohjelmatiedostojen ensimmäisistä riveistä (joita jyrkkä POSIXin tulkinta siis ei oikeastaan salli): #!/bin/bash, #!/bin/csh, #!/bin/ksh, #!/bin/perl, #!/bin/python, #!/home/nieminen/ohjelmia/ihan_oma_tulkkini.

Dollarimerkki ja muuttujat

Otetaan nyt tarkempi katsaus siihen, miten shellissä voi asettaa ja käyttää muuttujia (variable). Paljoltihan ohjelmointi perustuu muuttujien käyttöön. Muuttuja asetetaan seuraavasti:

mnimi="diiba daa"

Tässä ei saa olla välilyöntejä yhtäsuuruusmerkin ympärillä, koska muuten shell pilkkoo rivin palasiin ja yrittää suorittaa komentoa mnimi.

Muuttujan sisältö on shellissä aina merkkijono. Sisällön saa lavennettua komentorivin osaksi kirjoittamalla dollarimerkin $ ja välittömästi sen perään muuttujan nimen. Muuttujan voi hävittää komennolla unset. Esimerkki muuttujan määrittelystä, sisällön sijoittamisesta osaksi komentoriviä ja muuttujan hävittämisestä:

[nieminen@localhost esimerkki]$ mnimi="diiba daa"
[nieminen@localhost esimerkki]$ echo mnimi
mnimi
[nieminen@localhost esimerkki]$ echo "muuttuja on nyt:$mnimi."
muuttuja on nyt:diiba daa.
[nieminen@localhost esimerkki]$ unset mnimi
[nieminen@localhost esimerkki]$ echo "muuttuja on nyt:$mnimi."
muuttuja on nyt:.

Huomautuksia:

  • Tyhjä merkkijono ei tarkkaan ottaen ole sama kuin olemassa olematon muuttuja, vaikka esimerkissä tavallaan siltä näyttäisikin! Esimerkiksi komento set listaa tyhjät muuttujat nimeltä. Komennon unset mnimi jälkeen muuttujaa nimeltä mnimi ei kerta kaikkiaan löydy enää.

  • Miksi komennossa unset sitten ei ole dollarimerkkiä? Mietipä hetki ennen spoileria ... ... ... sitten jos ei selvinnyt, katso esimerkkiä ja mieti uudestaan:

    [nieminen@localhost esimerkki]$ diiba=hoi
    [nieminen@localhost esimerkki]$ mnimi="diiba daa"
    [nieminen@localhost esimerkki]$ echo $diiba $mnimi
    hoi diiba daa
    [nieminen@localhost esimerkki]$ unset $mnimi
    [nieminen@localhost esimerkki]$ echo $diiba $mnimi
    diiba daa
    

    Ei tässä mitään cliffhangeria tai spoileria oikeastaan ollut, vaan syntaksia ja sen tulkintaa: dollarimerkin jälkeen tuleva pätkä tulkitaan muuttujan nimeksi ja sisältö korvataan komentoriville ennen komentorivin suorittamista. Siis edellä suoritettiin komento unset diiba daa, joka tuhosi muuttujan nimeltä diiba, mikä oli tietysti huono juttu, jos komennon kirjoittaja oikeasti halusi tuhota muuttujan mnimi ja jättää diiba:n eloon. Itse asiassa myös muuttuja nimeltä daa olisi hävitetty.

Ympäristömuuttujat tulevat automaattisesti shellin ja skriptien käyttöön saman näköisinä kuin muutkin muuttujat: Dollarimerkin jälkeen voi kirjoittaa ympäristömuuttujan nimen, ja kyseisen muuttujan arvo laventuu syntaksin paikalle normaaliin tapaan. Demosta 3 tuttu esimerkki ympäristömuuttujasta:

[nieminen@localhost esimerkki]$ echo $LC_ALL
en_US.utf8

Demosta 3 muistetaan myös, että muuttuja päätyy uusien käynnistettävien ohjelmien käyttöön vain silloin kun se on viety (export) ympäristöön. Esimerkiksi:

mnimi="diiba daaba"
export mnimi

Näiden komentojen jälkeen kaikki tästä shellistä käynnistettävät skriptit voisivat käyttää muuttujaa syntaksilla $mnimi ja C-kieliset ohjelmat saisivat osoittimen merkkijonoon diiba daaba kutsulla getenv("mnimi").

Muita tapoja muuttujien syntymiseen ovat komennot read (nimetyn muuttujan arvon lukemiseksi syöttövirrasta), getopts (viputyyppisten argumenttien eli "optioiden" käsittelyn helpottamiseksi nimetyn muuttujan avulla) ja for -silmukkarakenne.

Shell-toteutukset saavat tietysti myös toteuttaa laajennoksena omia mekanismejaan muuttujien luomiseksi näiden edellämainittujen yhteensopivien tapojen lisäksi.

Dollarimerkki ja numeroidut argumentit

Dollarimerkin jälkeen voi tulla muuttujan nimen sijaan myös muutamia muita asioita. Yksi vaihtoehto on kymmenjärjestelmän numero 1-9, esimerkiksi $1, 2 tai $9 tai useampinumeroinen luku aaltosulkujen ympäröimänä, siis voisi olla ${10}, ${11} tai ${123} jne. Yleensä tarvitaan kohtalaisen pieniä lukuja. Nämä toimivat samoin kuin nimettyjen muuttujien yhteydessä, eli dollarisyntaksilla merkitty pätkä päättävään aaltosulkuun saakka korvautuu muuttujan sisällöllä. Nyt nämä numeroidut muuttujat vaan ovat erikoismerkityksessä: Skripti saa järjestetyt argumenttinsa näiden numeroitujen muuttujien sisältönä. Esimerkki:

[nieminen@localhost esimerkki]$ cat ripti.sh
#!/bin/sh
echo $1 $2
[nieminen@localhost esimerkki]$ ./ripti.sh arg1 arg2 arg3 arg4
arg1 arg2

Tässä skripti komentaa echo sen jälkeen kun komentorivillä on korvautunut $1 ensimmäisen argumentin sisällöllä, $2 toisen ja niin edelleen. Tämä skriptihän tosiaan tulostaa vain kaksi ensimmäistä, mutta loputkin olisivat saatavilla.

Olisi käyttäjän kannalta vaikeaa, jos apuohjelman argumentit olisi aina annettava juuri tietyssä järjestyksessä. Sen vuoksi nämä järjestyksen perusteella numeroidut muuttujat eivät ole kaikista näppärin tapa käydä käyttäjän antamaa argumenttilistaa läpi. Yksi parempi tapa nähdään tämän demon esimerkkiskriptissä, jota on tarkoitus matkia omassa palautustehtävässä.

Enemmän hyötyä numeroiduista muuttujista on silloin, kun shelliin määritellään aliohjelmia (joita sanotaan funktioiksi). Aliohjelmia kutsuttaessa numeroidut muuttujat nimittäin väliaikaisesti sisältävät aliohjelmalle annetut, tässä tapauksessa aina samassa järjestyksessä olevat parametrit.

Käyttötarkoituksensa vuoksi näitä numeroituja muuttujia $1, $2 jne. sanotaan "järjestetyiksi parametreiksi" (positional parameter).

Dollarimerkki ja erityiset parametrit

Dollarimerkin jälkeen voi tulla muuttujan nimen tai argumentin järjestysnumeron lisäksi muutakin. POSIXin mukaan voi tulla @, *, #, ?, -, $, ! tai 0. Listataan tässä varsin lyhyessä ja yksinkertaistetussa muodossa, mitä joidenkin näiden niin sanottujen erityisten parametrien (special parameters) paikalle laventuu komentoriville:

$0 = skriptin oma käynnistysnimi (vrt. C-kielen argumentti argv[0])

$@ = kaikki argumentit eli $1, $2, jne.

$# = käyttäjän antamien argumenttien lukumäärä ($0 ei mukana tässä lukumäärässä; tässä kohtaa laskutapa on eri kuin C-kielen pääohjelmassa, jossa lukumäärään argc lasketaan mukaan ohjelman oma nimi argv[0].)

$? = viimeisimmän komennon virhekoodi

$$ = skriptiä suorittavan shellin PID

Dollarimerkki ja lauseke aaltosuluissa

Dollarimerkin jälkeen voi tulla edelleen muutakin. Yhdistelmä ${ eli dollari ja aukeava aaltosulku aloittaa monipuolisemman parametrilavennoksen (parameter expansion), joka loppuu vastaavaan sulkeutuvaan aaltosulkuun }. Syntaksi on siis:

${lauseke}

missä lauseke tulkitaan erityisellä tavalla. Yksinkertaisin esimerkki lausekkeesta on yksittäinen muuttuja. Esimerkki:

echo "Kotihakemisto on ${HOME}, shellin PID on ${$} ja eka argumentti ${1}"

Edellä tätä jo käytettiinkin numeroitujen argumenttien osalta. Useampinumeroisten järjestyslukujen osalta lausekenotaatio on välttämätön. Samoin yksittäinen muuttujanimi on oltava aaltosulkeissa, mikäli muuttujan sisällön perään halutaan välittömästi merkkejä, jotka muutoin voitaisiin tulkita osaksi muuttujan nimeä. Esimerkki:

[nieminen@halava ~]$ muu=kissa
[nieminen@halava ~]$ echo ${muu}koira
kissakoira
[nieminen@halava ~]$ muu=Surku-
[nieminen@halava ~]$ echo ${muu}koira
Surku-koira
[nieminen@halava ~]$ echo $muukoira

[nieminen@halava ~]$

Viimeinen rivi tulostaa tyhjää, jos muuttujaa muukoira ei ole asetettu. Shell tulkitsee pisimmän mahdollisen nimen mukaan, eikä yritäkään löytää tässä määriteltyä nimeä muu osajonona. Välilyönnin ja rivinvaihdon lisäksi esimerkiksi operaattorimerkit katkaisevat muuttujan nimen tulkinnan, joten seuraavassa ei tarvitsisi aaltosulkunotaatiota:

[nieminen@halava ~]$ echo $muu|cat
Surku-

Koodin selkeyttämiseksi voipi laittaa vaikka kaikki muuttujanimet aina sulkuihin, niin ei ainakaan tule sekaannuksia.

Lausekkeeseen voi sisällyttää lisätoiminnallisuutta lisäämällä muuttujan nimen perään toiminnon symbolin ja lisukesanan. Esimerkkien kautta tässä ainakin muutamia näistä:

[nieminen@halava l14]$ elain="koira"
[nieminen@halava l14]$ echo "$elain on eläin."
koira on eläin.
[nieminen@halava l14]$ elain=""
[nieminen@halava l14]$ echo "$elain on eläin."
 on eläin.
[nieminen@halava l14]$ echo "${elain:-kissa} on varmasti eläin."
kissa on varmasti eläin.

Monissa olosuhteissa on kiva olla varma, että dollarisyntaksin paikalle varmasti laventuu jotain, vaikka ei tiedettäisi onko muuttuja epätyhjä tai onko sitä ylipäätään asetettu. Ylläolevalla :- -notaatiolla varmistetaan, että sen perässä oleva sana tulee lavennukseen, jos varsinaisesta muuttujasta ei tulisi yhtään merkkejä. Näin ei tarvita mitään ylimääräisiä ``if`` -rakenteita tarkistuksen tekemiseksi, jos oletusarvon käyttäminen riittää. Jatketaan toisella esimerkillä:

[nieminen@halava l14]$ elain=""
[nieminen@halava l14]$ echo "${elain:=kissa} on sitten muuten jatkossakin varmasti eläin."
kissa on sitten muuten jatkossakin varmasti eläin.
[nieminen@halava l14]$ echo "Tai siis muuttuja, jossa on \"$elain\" on varmasti asetettu ja epätyhjä"
Tai siis muuttuja, jossa on "kissa" on varmasti asetettu ja epätyhjä

Tämä vahvempi syntaksi := myös asettaa muuttujan, jos sen arvo oli tyhjä tai jos sitä ei ollut olemassa. Mikäli muuttuja oli jo aiemmin asetettu epätyhjäksi, muutosta ei tapahdu, vaan alkuperäistä sisältöä käytetään:

[nieminen@halava l14]$ elain=kahvikuppi
[nieminen@halava l14]$ echo "${elain:=kissa} on sitten muuten jatkossakin varmasti eläin."
kahvikuppi on sitten muuten jatkossakin varmasti eläin.

Jos oletusarvon käyttäminen ei riitä, vaan toiminnon toteuttaminen vaatii jyrkästi jonkin muuttujan olemassaolon, niin voidaan käyttää syntaksia :? seuraavasti:

[nieminen@halava l14]$ unset elain
[nieminen@halava l14]$ echo "${elain:?"Ja jos ei oo eläintä, niin livistetään"} on varmasti eläin."
bash: elain: Ja jos ei oo eläintä, niin livistetään

Jos muuttujaa ei ole tai sen sisältö on asetettu tyhjäksi merkkijonoksi, niin homma loppuu virheilmoitukseen saman tien ennen kuin komentojonoa tulkitaan sen enempää. Tätä voi käyttää skriptin tekemisessä, jos halutaan, että se "kaatuu" automaattisesti puuttuvan muuttujan tullessa vastaan. Erillistä testiä ei tässäkään tarvitse kirjoittaa, koska kompakti syntaksi hoitaa suoraviivaisesti sekä normaalin tilanteen että poikkeavan.

Mikäli halutaan käyttää "vaihtoehtoista arvoa" eli nimenomaan jotakin muuta kuin mitä muuttujassa mahdollisesti on, voi käyttää syntaksia :+ seuraavasti:

[nieminen@halava l14]$ unset elain; echo "Tässä on sitten kissa tai ei mitään: ${elain:+kissa}"
Tässä on sitten kissa tai ei mitään:
[nieminen@halava l14]$ elain=koira; echo "Tässä on sitten kissa tai ei mitään: ${elain:+kissa}"
Tässä on sitten kissa tai ei mitään: kissa

Kaikista edellisistä on olemassa miedompi variaatio, jossa ei käytetä kaksoispistettä. Silloin tyhjä merkkijono tulkitaan käyttökelpoiseksi muuttujan sisällöksi. Riippuu käyttötarkoituksesta, kumpi on tarkoituksenmukaista.

Lausekkeen muodolle on näiden lisäksi muitakin vaihtoehtoja, joita kiinnostuneet voivat tutkia standardista tai oman shellin manuaalista. (Mieluiten standardista, koska silloin ei juutu kiinni oman shellin laajennoksiin.)

Esimerkki vain muutamasta lisäsyntaksista:

[nieminen@halava l14]$ elain=kissakoira; echo "Eläimen mitta on ${#elain} merkkiä"
Eläimen mitta on 10 merkkiä

Syntaksi on kieltämättä aika vivahteikasta... Jos on risuaita # ja sen jälkeen muuttujan nimi, niin tulostuu muuttujan sisällön pituus. Pelkkä risuaita ${#} puolestaan on tosiaan skriptin argumenttien lukumäärä. Muuttujan nimen jälkeen risuaidalla on taas aivan eri merkitys:

[nieminen@halava l14]$ echo ${elain%ra}kas ${elain#kis} ${elain#ki*ak}
kissakoikas sakoira oira
[nieminen@halava l14]$ hahmo="ki*ak"; echo ${elain#$hahmo}
oira

Syntaksilla % voi poistaa muuttujan lopusta ja syntaksilla # alusta hahmoja (pattern), joissa voi käyttää esimerkiksi villikorttisymbolia *. Hahmo puolestaan voi olla muuttujan sisältönä, josta se laventuu lausekesyntaksin sisään ennen kuin se tulkitaan loppuun. Tällä voi loppujen lopuksi tehdä kompaktisti kirjoitettuja kohtalaisen jänniä (ja hyödyllisiä) muunnoksia muuttujille suoraan niissä paikoissa, joissa muunnettuja sisältöjä tarvitaan. Esimerkiksi tiedostopäätteiden poistoa tiedostonimistä tai muuta vastaavaa. Tai hakemisto-osan poistamista kahdella risuaidalla:

[nieminen@halava l14]$ echo $PWD; echo ${PWD##*/};
/nashome3/nieminen/charragit/itka203-kurssimateriaali-avoin/2015/esimerkit/l14
l14

Dollarimerkki ja komentokorvaus

Demoissa on alusta lähtien käytetty graviksia eli "takahipsuja" esimerkiksi seuraavalla tavoin:

echo "Tänään on `date`"

Syntaksi on nätti ja näppärä. Sillä suorittuu ensin komento, jonka tuloste laventuu osaksi myöhemmin suoritettavaa komentoriviä. Tämä on nimeltään komentokorvaus (command substitution); komennon tuloste korvaa pätkän alkuperäisestä komennosta.

Esimerkillä oli tarkoitus alusta lähtien havainnollistaa tietoteknisten ympäristöjen tarkkuutta syntaksin suhteen. Ei ole millään tavalla sama, kirjoitetaanko ', ' vai ", vaikka merkit saattavat äkkiseltään näyttää silmään hyvin samalta.

Samoin on varmasti aina eri asia kirjoittaa tietokoneella l (äl) tai 1 (yksi), 0 (nolla) tai O (iso oo) tai o (pieni oo). Merkittäköön muistiin (kieli poskessa ja kaveria viimeisen päälle kunnioittaen), että kevään 2015 kurssilla oli tuntiopettajallakin kertaalleen sormi suussa, kun demotilaisuudessa seuraava komento "ei toiminut":

c99 -g -o0 ohjelma.c

Pienen puhelinneuvottelun jälkeen saatiin myös tuntiopettaja lukemaan tehtävän ohjeessa välittömästi esimerkin jälkeen annettu selitys, jossa sanottiin, että haluttu argumentti oli "-O0 (eli iso oo-kirjain ja nolla)". Komento oli toki toiminut, ja ohjelma oli käännetty binääritiedostoksi, jonka nimi ("output file name", '-o') oli 0. Ei siis mitään hätää.. homma jatkui kokeilemalla suorittaa juuri käännetty ohjelma:

./0

Seuraavaksi tapahtuu paluu reunahuomautuksesta (return from digression)...

Gravis-lainauksen sijasta voidaan shellissä käyttää myös syntaksia, jossa on dollarimerkki ja avautuva kaarisulku. Suoritettava komento päätetään sulkeutuvalla kaarisululla. Edellä ollut takahipsuesimerkki voitaisiin yhtä hyvin kirjoittaa seuraavasti:

echo "Tänään on $(date)"

Syntaksissa oleva dollarimerkki tekee siitä ehkä yhdenmukaisemman muiden lavennussyntaksien kanssa, joten se voi olla todellisissa sovelluksissa mielekkäämpi kuin "muuten niin nätti" gravis-notaatio. Kyseessä voi olla myös makuasia, tyyliseikka, tai tilannesidonnainen valinta. Molempia syntakseja tulee vastaan, ja täytyy tietää, mitä kumpikin niistä tekee.

Komento suoritetaan ns. alishellissä (subshell), joka perii nykyisen shellin ympäristön (ympäristömuuttujat ym.).

Komento voi itsessään olla skripti. Esimerkiksi:

echo $(echo ja; echo tuota; echo kissa; echo koira)

Aivan samoin:

echo `echo ja; echo tuota; echo kissa; echo koira`

Komennossa voi olla sisällä komentoja (ja niiden sisällä komentoja):

echo $(echo $( echo ja; echo tuota; echo kissa; echo koira) )

Aivan samoin (paitsi, että sisemmissä takahipsuissa pitää olla edellä kenoviiva, koska muuten shell luulee että ulompi komento loppuu ensimmäisenä vastaan tulevaan takahipsuun):

echo `echo \` echo ja; echo tuota; echo kissa; echo koira\` `

Vaikka tällaiset sisäkkäiset rakenteet ovat mahdollisia, niitä tuskin tarvitsee aivan joka päivä. Kaikenlaista monimutkaisuutta tulee välttää, mikäli se ei ole ratkaistavana olevan ongelman kannalta välttämätöntä.

Miksi muuten aina echo näissä esimerkeissä? No se nyt tekee jotain selvästi ymmärrettävää, niin voidaan keskittyä ympäröiviin rakenteisiin. Todellisia esimerkkejä hyötykäyttöön tehdyistä skripteistä löytyy lähimmillään tällä kurssilla käytettyjen palvelinkoneiden ylösajo- ja ohjelmankäynnistysskripteissä. On sielläkin echo siellä ja toinen täällä, mutta myös monia muita komentoja ja rakenteita.

Komennon tulosteessa olevien välimerkkien ja rivinvaihtojen hallittu käsittely vaatii tarkkaavaisuutta. Spesifikaatio on syytä lukea tarkkaan, mikäli aikoo käyttää alishellissä komentoa, jonka tulosteessa on odotettavissa rivinvaihtoja tai muita merkityksellisiä välimerkkejä.

Esimerkiksi tämän kurssin ensimmäisessä demossa oli kummallisen näköisiä tulosteita, joiden olisi pitänyt olla ps -ohjelman muotoilemia. Rivinvaihdot ja muut välimerkit olivat monessa vastauksessa hukassa, koska oli yritetty tehdä asia mutkan kautta liian monimutkaisesti:

echo `ps -f`

Tuloste tulee silloin echo -ohjelmalta, jonka argumenteiksi on korvattu ps -f:n tulosteen välimerkein erotetut osat. Tulosteen olisi saanut näyttämään jälleen oikealta seuraavasti:

echo "`ps -f`"

Nyt komennon tuloste laventuisi lainausmerkkien sisällä, jolloin se menisi echo -ohjelmalle yhtenä yksittäisenä argumenttina, jossa on sisällä myös välimerkit. Toimintamalli on kaikessa "luovuudessaan" silti liian monimutkainen, jos halutaan pelkkä ps -f:n tuloste.

Riippuu tarkoituksestasi, haluatko käyttää lainausmerkkejä vai antaa shellin purkaa komentokorvaustulosteen automaattisesti osiinsa (ns. kenttiin jako, field splitting). Kenttiin jakamisen välimerkkiä voi vaihtaa, mikäli jostain syystä tarvitsee:

[nieminen@localhost esimerkki]$ echo `ps`
PID TTY TIME CMD 3244 pts/6 00:00:01 bash 7896 pts/6 00:00:00 ps
[nieminen@localhost esimerkki]$ echo "$(echo `ps` )"
PID TTY TIME CMD 3244 pts/6 00:00:01 bash 7918 pts/6 00:00:00 bash 7919 pts/6 00:00:00 bash 7920 pts/6 00:00:00 ps
[nieminen@localhost esimerkki]$ echo "$(IFS=":"; echo $(ps) )"
  PID TTY          TIME CMD
 3244 pts/6    00 00 01 bash
 7972 pts/6    00 00 00 bash
 7973 pts/6    00 00 00 bash
 7974 pts/6    00 00 00 ps
[nieminen@localhost esimerkki]$ echo "$(IFS=":I/"; echo $(ps) )"
  P D TTY          T ME CMD
 3244 pts 6    00 00 01 bash
 8027 pts 6    00 00 00 bash
 8028 pts 6    00 00 00 bash
 8029 pts 6    00 00 00 ps
[nieminen@localhost esimerkki]$ echo $(IFS=":/I"; echo $(ps) )
P D TTY T ME CMD 3244 pts 6 00 00 01 bash 8197 pts 6 00 00 00 bash 8198 pts 6 00 00 00 bash 8199 pts 6 00 00 00 ps

Tässä on esimerkkejä lainausmerkkien vaikutuksista komentokorvaukseen. Ensimmäisessä echo saa argumentteinaan ps:n välilyönneillä, tabulaattoreilla ja rivinvaihdoilla erotetut osat. Toisessa sama tapahtuu alishellissä (jonka PID muuten nähdään ps:n tulosteessa, joten se on pidempi kuin edeltävä), mutta ulomman shellin echo saa argumentikseen vain yhden merkkijonon, koska koko komentokorvauslauseke on lainausmerkeissä. Seuraavassa esimerkkikomennossa pilkotaankin syvimmän komentokorvauksen tulosteita kaksoispisteen kohdalta, eikä ollenkaan valkoisten merkkien eikä rivinvaihtojen. Toiseksi viimeisessä esimerkkikomennossa pilkotaan syvimmän alishellin syötettä kolmen eri merkin kohdalta, eli kaksoispisteen, kauttaviivan ja ison I-kirjaimen. Viimeinen esimerkki havainnollistaa alishellin merkitystä: Pilkkomismerkin säätö muuttujalla IFS vaikuttaa vain alishellissä. Ulompi komentokorvaus ilman lainausmerkkejä toimii kuten ensimmäisessäkin esimerkissä tyhjämerkkien kohdalta pilkkoen.

Nollamerkkejä (vrt. C-merkkijonon lopun koodaus) sisältävien tulosteiden osalta POSIX jättää määrittelemättä, mitä pitäisi tapahtua. Muita kuin tekstiksi koodattuja tulosteita ei siis kannattaisi yrittää. Moniin hyödyllisiin tarkoituksiin riittää kuitenkin sellaisen apuohjelman tuloste, joka tulostaa vain yhden sanan. Esimerkiksi hostname, uname, logname

Dollarimerkki ja kokonaislukuaritmetiikka

Dollarimerkin jälkeen voi tulla kahdet peräkkäiset avaussulut eli yhdistelmä $(( ilman välilyöntejä. Vastaavaan kaksinkertaiseen päättävään sulkuun eli )) saakka on aritmeettista lausekketta, jossa voi laskea kokonaisluvuilla, joiden lukualue on vähintään niin suuri kuin C-kielen signed long. Luvuilla voi laskea melkein kaikilla C99-standardin määrittelemillä kokonaislukuoperaatioilla (paitsi ++, -- ja sizeof()). Esimerkki:

echo "yksi plus kaksi on $((1+2))."
echo "kaksi kertaa kolme on $((2*3))."

Hyödyllisempää homma on muuttujia käyttämällä. Lausekkeen sisällä dollarimerkkiä saa käyttää muuttujille, mutta tässä ei ole pakko, koska tuplasulun jälkeen shell tulkitsee jo aritmetiikkaa eikä normaalia osuutta komentorivistä:

a=5; b=9; echo "aa plus bee on $((a+b))"      # lyhyt notaatio
a=5; b=9; echo "aa plus bee on $(($a+${b}))"  # sama merkitys

Ohjelmoinnin osalta on erityisen hyödyllistä, että lausekkeessa voi sijoittaa arvoja muuttujiin pysyvästi. Esimerkki:

a=5; b=9; echo "aa plus bee on $((c=a+b)), mikä on nyt c:ssä"; echo $c

Skripti, joka tulostaa positiiviset 16:n potenssit, jotka mahtuvat shellin aritmetiikan lukualueeseen:

a=1; while [ $a -gt 0 ]; do echo $((a<<=4)); done

Tulosta, mitä on heksaluku 0xabcde kymmenjärjestelmässä (oletusmuoto tulosteelle on 10-järjestelmä, mutta lausekkeessa voi käyttää oktaali- ja heksamuotoja):

echo $((0xabcde))

Kenttien jakaminen lavennusten jälkeen

Edelläkin jo alustavasti spoilattiin tätä. Dollari- ja gravislavennusten jälkeen shell tekee vielä kenttiin jakamisen (field splitting) tiettyjen sääntöjen mukaan. Oletuksena tyhjämerkeillä (<välilyönti>, <tabulaattori>, <rivinvaihto>) erotetut pätkät tulevat jokainen omaksi kentäkseen (field), esimerkiksi tulevien komentojen argumenteiksi. Jos oletusta ei haluta käyttää, voi asettaa ympäristömuuttujaan IFS haluamansa välimerkit (tai tyhjän merkkijonon, jos ei halua jakamista ollenkaan tapahtuvan). Alusta ja lopusta shell nielaisee välimerkit pois.

Tiedostonimien laventaminen

Kenttien jakamisen jälkeen shell tutkii lavennoksessa syntyneet kentät vielä olemassaolevien tiedostonimien varalta tietyn hahmosyntaksin mukaan. Esimerkkejä:

echo *

Tässä komennon echo argumenteiksi laventuisi kaikki oleskeluhakemistosta löytyvät tiedostonimet, jotka eivät ala pisteellä (ts. nykyhakemisto ., ylähakemisto .. tai käyttäjän pisteellä "piilottamat" tiedostot eivät lavennu komentoriville). Nämä saa näkyville seuraavasti:

echo .*

Tässä laventuu ainoastaan pisteellä alkavat. Tietenkin näitä voi yhdistellä normaalilla tavalla:

echo * .*

Lavenna tiedostot, joiden nimi alkaa merkeillä kayttoj:

echo kayttoj*

Lavenna tiedostonimet, joiden nimessä on alussa kayt, lopussa .tex ja välissä mitä tahansa merkkejä:

echo kayt*.tex

Listaa pitkässä formaatissa tiedostot, joiden pääte on .c:

ls -l *.c

Tiedostot, joissa on kysymysmerkin kohdalla mikä tahansa merkki:

ls -l d0?.txt

Lainausmerkkien poistaminen

Kuten on nähty, lainausmerkkien tehtävä on kiinnittää komentorivin osia yhtenäisiksi kirjoittajan haluamin tavoin. Itse lainausmerkit shell poistaa komentoriviltä ennen komennon suorittamista, joten jäljelle jää vain lainausmerkkien sisällä olleet palaset.

Alishell

Esim:

( echo kissakoira )

Tilde eli "matomerkki"

Tilde ~ eli "matomerkki" yksinään tai kauttaviivan edellä laventuu kuten ympäristömuuttuja HOME dollarimerkkinotaatiossa $HOME. POSIX ei määrittele, mitä tildelle tapahtuu, jos HOME ei ole asetettu. Yleisesti ottaen merkin ~ tilalle tulee oman kotihakemiston nimi, joten sen avulla pääsee näppärästi omiin tiedostoihin käsiksi. Esimerkiksi ls ~/kurssit/itka203/.

Tildellä saa lavennettua myös toisen käyttäjän kotihakemiston (tarkkaan ottaen aloitustyöhakemiston, johon kyseinen käyttäjä ensimmäiseksi putoaa kirjautuessaan päätteelle). Voit kokeilla halavassa:

echo ~nieminen ~totalund ~arsatuhk ~

Pittäis tulostua kevään 2015 opettajakunnan kotihakemistojen nimet ja sitten vielä omasi. Tilde kuuluu erikoismerkkeihin, jotka eivät toimi lainausmerkkien sisällä. Tästäkin voi kokeilemalla varmistua:

echo "~nieminen ~totalund ~arsatuhk ~"

Kotihakemiston laventaminen lainausmerkkien sisällä tarvitsee siis jonkin muun keinon. Omasi saat esimerkiksi ympäristömuuttujasta HOME, mikäli se on normaalilla tavalla asetettu:

echo "$HOME"

... TODO: POSIXista suomeksi tähän.

PUUTTUVA OSUUS: POSIXin shell-määritelmän referointia suomeksi lisää

Esimerkkejä potentiaalisista kummallisuuksista

Shellin syntaksi on aivan tarkoin määritelty, mutta siinä on aika monia erillisiä sopimuksia siitä, missä vaiheessa mikäkin lavennos tehdään. On poikkeuksia perussäännöistä ja ainakin poikkeuksen poikkeuksia. Kolmannen tason poikkeuksen poikkeuksen poikkeuksia tämän kirjoittaja ei muista ainakaan nähneensä säännöissä.

Kaikkia kommervenkkejä ei ole järkeä listata tässä. Alkuperäinen määritelmä on joka tapauksessa tarkistettava, jos aikoo tehdä jotakin erikoisempaa. Tässä muutama esimerkki, jotka saattavat hämmentää ihmisen mieltä aluksi ja aiheuttaa virheellistä toiminnallisuutta, jos ei muista tarkistaa speksiä ennen kuin tekee:

[nieminen@halava l14]$ ls
a.out  cat  hello  hellofile  hellofile.c  hmm2  hmm3  kortexilaiset  liit  liit.c
[nieminen@halava l14]$ echo hel*
hello hellofile hellofile.c
[nieminen@halava l14]$ hmm=hel*
[nieminen@halava l14]$ echo $hmm
hello hellofile hellofile.c
[nieminen@halava l14]$ echo "$hmm"
hel*

Hakemistossa on luentoesimerkkejä tiedostoissa. Asteriski laventuu normaalisti. Sitten tehdään muuttuja hmm, johon sisällöksi laitetaan hel*. Onko tähti laventunut? Todellisuus ei selviä meille kokeilemalla echo $hmm koska tulostehan on sama kuin jos hel* olisi jo lavennettu. Mutta eipä olekaan. Se selviää, kun laitetaan muuttuja lainausmerkkeihin, joiden sisällä asteriski toimii omana itsenään eikä lavennu tiedostonimiksi. Muuttujan asetuksessa ei ole tehty tiedostonimien lavennusta, vaikka ei olekaan kirjoitettu esim. hmm="hel*". Asteriski on osa muuttujan sisältöä, joka on lavennettu dollarinotaatiolla nimestä. Tiedostonimilavennus tehdään vielä sen jälkeen, käyttäen muuttujasta pullahtaneita erikoismerkkejä! Ei ole mitenkään päivänselvä logiikka, mutta näin tämä järjestys nyt on tässä syntaksissa määritelty... Joutuu tietämään, mitä haluaa laventuvan minnekin, ja lisäillä tarvittaessa lainausmerkkejä.

POSIXin shellistä määrittelemiä asioita, joita tässä ei mainittu

Seuraavat jätettiin yksinkertaisuuden vuoksi mainitsematta: