A shell, mint parancsértelmező tulajdonságai

A Korn shell rendelkezik mindazokkal a parancsértelmező képességekkel, amivel a Bourne shell. Tehát lehet vele a standard ki- és bemenetet stb. átirányítani, parancsokat csőhálózatba összefűzni, valamint vannak a parancshatárolók, mint ';' (pontosvessző, azaz a sorozatbani végrehajtás jele), '&&' (logikai ÉS kapcsolat, a második utasítást csak az első sikeres futása után indítja) végül a '||' (logikai vagy, a második utasítást csak az első sikertelen futása esetén indítja), valamint képes shellscript-ek futtatására is. Mindebből részletesen csak arról szólunk ami valamiben eltér a Bourne shell-beli megoldástól, vagy annál többet nyújt. Így nem kell külön foglalkoznunk avval, hogyan illeszti a legtöbb metakaraktert (*,?, [...]) a shell az állománynevekre, és az idézőjelek használatával. A specialitásokat az alábbiakban foglaljuk össze:

Kétirányú csőhálózat

A kétirányú csőhálózat, népszerűbben kétirányú pipe, megértéséhez előbb a normál egyirányú pipe fogalmát kell megértenünk. Az egyszerű csőhálózat lényege, hogy két folyamat egy FIFO-n keresztül kommunikál egymással, az

$ ls -l | grep '^d'

parancssor például csak a katalógusokat írja a képernyőre. A folyamatot a kernel vezérli, feladata az, hogy az ls ne töltse túl a FIFO-t, a grep viszont várakozzon, ha a FIFO üres. Kétirányú pipe esetén a Korn shell egy olyan folyamatot hoz létre, ami a be- és kimenetét egyaránt a szülő shell-től kapja, egy FIFO-n keresztül.

Kétirányú pipe-ot a parancs után tett- '|&' karakterpárral tudunk létrehozni, ami után shell-ből a print -p és read -p parancsokkal tudunk a folyamattal kapcsolatot tartani.

Nyilvánvaló, hogy ezt a lehetőséget leginkább interaktív módon működő utasítások esetén lehet kihasználni, ilyen azonban a UNIX-ban nem sok van. Szintén jól használható ez az eszköz, ha közbenső állomány létrehozását akarjuk elkerülni.

A nagyabc nevű script a tr parancsot használja fel a kétirányú pipe-hoz. A bemenetére írt sorokat a kimenetére írja, miközben a nagybetűvel kezdődő sorok összes magánhangzóját szintén nagybetűre cseréli. (Nem túlságosan izgalmas feladat, de talán a kétirányú csőhálózat alkalmazásának egy lehetőségét bemutatja.)

$ cat nagyabc

tr [aeiou] [AEIOU] |&

while read a

do

if echo $a|grep '^[A-Z]' >/dev/null

then print -p $a

read -p a

fi

echo $a

done

$

A nevjegyzek hasonló módon hívja az általunk írt keres nevű parancsot, hogy megnézesse vele, hogy a bemenetére küldött név szerepel-e a cimlist fájlban. A példa ugyan bugyuta, a feladatot egyszerűbben is meg lehet oldani, inkább csak meg kívántuk mutatni, hogy hogyan dolgozik egy magunk által írt script kétirányú pipe segítségével.

$ cat keres

read a

if grep $a cimlist >/dev/null

then print -p "igen"

else print -p "Kerem a $a cimet:\c"

fi

$

$ cat nevjegyzek

while read a

do

keres |&

print -p $a

read -p b

if [ "$b" = "igen" ]

then

echo "Van ilyen nev a listaban"

* else

echo $b

read b; echo $a : $b>>cimlist

fi

done

$

Az echo parancs argumentumába írt \c a soremelést tiltja be.

Állománynév kiegészítések

Emlékszünk rá, hogy a Bourne shell-ben bizonyos esetekben nem kellett kiírnunk az állományok neveit, hanem a csillagot vagy kérdőjelet tartalmazó argumentum helyére a shell behelyettesítette a katalógusban lévő állománynevekből azt amire a minta illett, majd az így kifejtett parancsot adta át a kernelnek végrehajtásra. Az állománynév kiegészítéssel a Korn shell egy olyan eszközt ad, amivel parancsvégrehajtás előtt visszakapjuk a mintánkra illeszkedő állományneve(ke)t, ami(ke)t ezek után elfogadhatunk, vagy átírhatunk. Ezt a szolgáltatást az ESC és még egy másik billentyű megnyomásával lehet kiváltani.

Lehetőségeink az alábbiak.

Két ESC leütésére felkínálja az illeszkedő nevet, ha csak egy van, ha pedig a keresett minta több névre is illeszkedik, a shell a leghosszabb közös részt írja vissza. Ezt a továbbiakban tetszőlegesen módosíthatjuk.

Az ESC= karakterpáros leütése után a shell felsorolja az összes illeszkedő nevet, és az utasítás megismétlésével várja, hogy a hiányzó karaktereket kiegészítsük. Ahhoz, hogy ezt megtehessük, előbb az 'a' karaktert (append) kell leütnünk, ekkor a vi 'append' módjához hasonlóan továbbírhatjuk a nevet.

Az ESC* karakterpáros után a shell egy sorban ajánlja fel az illeszkedő neveket:

Merőben más lehetőség, amikor változók értékeit íratjuk be a shell-lel, a változó neve után leütött két ESC karakterrel.

A fenti lehetőségek nem csak állománynevekre, hanem katalógus útvonalakra is alkalmazhatóak.

Tilde jel behelyettesítése

A tilde (~) karakternek az előfordulás helyétől függően az alábbi értékei lehetnek:

- Egyedül állva értéke a HOME változó tartalmával egyenlő.

$ echo $HOME

/home/book

$ echo ~

/home/book

$

- Ha egy + jel követi, a PWD tartalmával lesz azonos, ha - jel, akkor az OLDPWD-vel.

$ cd fejezet

$ pwd

/home/book/fejezet

$ echo ~+

/home/book/fejezet

$ echo ~-

/home/book

$

- Ha egy /-el lezárt karaktersor követi, akkor megkeresi az /etc/passwd állományban, hogy talál-e ilyen alap nevű felhasználót, ha igen, akkor az ő alapkatalógusának teljes elérési útvonalát adja vissza.

$ ls ~book/fejezet

Korn vi Csh

$ ls /home/book/fejezet

Korn vi Csh

$

Változók és a paraméter helyettesítések

Kétfajta változó van, a pozicionális, amire most is sorszámmal hivatkozunk, és a névvel azonosítható. A shell-be beépített névvel azonosított változókról már esett szó.

A Korn shell-ben a változóknak deklarálhatunk típust, illetve használhatunk tömböket. A típusdeklarálás a typeset paranccsal történik, amit a parancsok közt ismertetünk. A típusdeklarációval bevezetett változóra a shell vigyáz, hogy ne kaphasson más típusú értéket.

$ typeset -i a

$ a=qq

ksh:qq:bad number

$ a=1+1

$ echo $a

2

$

A változók értékének már ismert helyettesítési szintaktikája kissé módosult.

$ echo $par

Semmit sem látunk, mert ilyen nevű változónak még nem adtunk értéket

$ echo ${par:-"par ures"}

par ures

$ echo $par

 

$

Beírja a helyettesítő értéket, de a változót beállítatlanul hagyja.

$ echo ${par:=value}

value

$ echo $par

value

$

A változó értéket kap, ami ki is íródik az outputra.

$ echo ${par:+masik}

masik

$ unset par

$ echo ${par:+ujabb+}

$

Az értéket kapott változó helyett a helyettesítő érték íródik be, az üreset egy szóköz jelzi.

$ echo par

 

$ echo ${par:?value}

value

login:

Ha a változó még nem kapott értéket, a helyettesítő érték íródik be és kiléptet a shell-ből. Ha nem adunk helyettesítő értéket és üres a változó, egy "behuzalozott" üzenet jelenik meg.

Az előbbi kifejezésekben használt kettőspontot ha elhagyjuk, elmarad an-nak vizsgálata, hogy a változó kapott-e már értéket. (A Bourne shell-ben ez a vizsgálat nem iktatható ki.) További lehetőség a paraméterek értékének helyettesítésére a minta segítségével történő kivágás. Ennek általános alakja:

${parameter##minta} illetve ${parameter%%minta}

$ echo $HOME

/home/un

$ echo $PWD

/home/un/book

$ c=${PWD#${HOME}}

$ echo $c

/book

$

A # hatására a shell a paraméter értékének elejére illeszti azt a mintát, ami a # másik oldalán talál, és csak a nem illeszkedő részeket írja ki. Egy # esetén a legrövidebb, kettő esetén a leghosszabb illeszkedést keresi

$ echo ${x%:*}

noveny:gyumolcs

$ echo ${x%%:*}

noveny

$

A '%' karakter esetén az illesztés logikája hasonló az előbbi esethez, de a levágandó mintát hátulról keresi.

Mindkét esetre igaz, hogy ha a shell nem talál illeszkedést, a paraméterek teljes értékét írja vissza.

Shell változók tömbjét létrehozhatunk típusdeklarációval vagy értékadással. A C nyelvben használatossal megegyező módon a tömbindex számozása 0-val kezdődik, a tömbelemre pedig szögletes zárójelbe tett indexszel lehet hivatkozni.

$ typeset -u a[2]

$ b[0]=DES

$ b[1]=szilva

$ a[1]=EUKLI

$ echo ${a[1]}${b[0]} megírta az elemeket

EUKLIDES megírta az elemeket

A változókkal kapcsolatban meg kell említenünk néhány speciális jelentésű karaktert, illetve jelölést. A Bourne shell-hez hasonlóan a $* illetve a $@ tartalmazza a shellscript összes paraméterét, a $1-essel kezdve, szóközökkel elválasztva.

Ha elemek egy változótömb, akkor a következők lehetségesek.

$ elemek[0]=csok

$ elemek[1]=or

$ elem=csokor

$ echo ${elemek[*]}

csok or

$

A kifejezés az elemek tömb elemeit írja ki.

$ echo ${#elem}

6

$

Ez a kifejezés az elem helyettesítési értékében a karakterek számát írja ki.

$ echo ${#elemek[*]}

2

$

A visszaadott érték az elemek tömb elemeinek száma.

Utasítások behelyettesítése

Utasítások helyettesítésére Bourne shell-ben használt visszafele dőlő idézőjel, `parancs`, forma mellé új alak lép be: $(parancs).

$ echo "A bejelentkezett felhasznalok: $(date; who|sort)"

Fri Jul 30 10:22:03 EDT 1993

jani console Jul 30 09:35

un tty01 Jul 30 09:00

$

A zárójeles alak egymásba egyszerűbben skatulyázható mint az idézőjeles. Így az alábbi két alak egyenértékű:

$ echo $(echo $(echo szia))

szia

$ echo `echo \`echo szia\` `

szia

$

Ezzel az írásmóddal egyszerűsítéseket lehet az utasítások írásába vinni, ha épp valakinek erre szottyan kedve. Az itt következő mindhárom esetet a shell elfogadja és azonosan értelmezi.

$ echo "\n$(cat file)"

$ echo "\n $(<file)"

$ echo `<file`

Végül nézzünk egy érdekes példát a pozicionális paraméterek helyettesítésére is. Az alábbi sor egy shellscript belsejéből való és az X-edik pozicionális változó értékét íratja ki.

x=1

echo " a $x valtozo erteke $(eval echo \$$x)"

Gondoljuk meg mit írna ki az echo az eval függvény nélkül! A fenti echo paranccsal azonos outputot adó egysornyi echo parancsot Bourne shell-ben nem is tudunk írni. Ezen is elgondolkodhatunk, hogy vajon miért?

Alias-ok és függvények

A shell-ben az alias parancs segítségével egy úgynevezett alias vagy álnév táblát lehet feltölteni. A táblázat minden bejegyzése egyenlőségjellel összekapcsolt két karakterlánc. A shell a parancs végrehajtásakor rendre megkeresi, hogy a parancssor elemei szerepelnek-e valamelyik bejegyzés bal oldalán, és ha igen, akkor helyettesíti az egyenlőségjel jobb oldalán lévő karakterlánccal. Nagyon fontos tulajdonság, hogy minden parancs értelmezésénél csak egyetlen egyszer nézi meg a shell ezt a táblázatot.

Így az alias-ok használata egy újabb lehetőség arra, hogy a UNIX felhasználói felületét komfortosabbá tegyük a magunk számára.

Azért, hogy felhasználási példákat lássunk, nézzük meg egy alias tábla egy részletét:

$ alias

false=let 0

functions=typeset -f

history=fc -l

integer=typeset -i

r=fc -e -

true=:

type=whence -v

ls=/bin/ls

who=who|sort

$

A fenti lista az utolsó két sortól eltekintve a shell-ben kiinduláskor is benne lévő alias-okat mutatja. Magyarázatra az 'ls=' kezdetű sor szorul. Itt arról van szó, hogy ha az utasítás helyett a teljes útvonalat adjuk meg, ahol a parancs elérhető, akkor a parancsvégrehajtás idejét rövidítjük meg. Miért? Gondoljunk arra, hogy a végrehajtandó állományokat a shell a PATH tartalma szerinti helyeken keresi. Mielőtt tovább olvasná az olvasó a fejezetet, megkérjük, hogy gondolatban keresse meg a shell opciók között a track-et.

Az alias-ok használatakor az ember úgy érzi, hogy jó lenne, ha szimbolikusan hivatkozhatnánk az utasítás argumentumaira is. Mivel itt egyszerű táblázat alapján történő behelyettesítésről van szó, ezt nem tudjuk megtenni. A hiányt pótolni lehet a függvények használatával. A prompt után írt eddig még nem használt kulcsszó és az utána tett nyitó-csukó zárójelpár jelzi a shell-nek, hogy függvényt akarunk definiálni. Amennyiben szándékunkat megérti, a return leütése után a folytatósori prompt jelenik meg a képernyőn. A függvény magját a sor elejére írt kapcsos zárójel pár közé írjuk, szintaktikailag a shellscript-ekkel azonosan. Lássunk egy példát, ahol a cd parancs végrehajtását úgy bővítjük ki, hogy a promptban megjelenjen az aktuális katalógus.

$ cdd()

>{ cd $1; PS1='! $PWD $'

> }

$ cdd /bin

123 /bin $

Megjegyzendő, hogy a függvény belsejében is használhatunk alias-okat. Ne feledjük, hogy az interaktívan definiált alias-ok és függvények csak a shell-ből való kilépésig élnek, ezért azokat, amelyeket mindig használni kívánunk, a konfigurációs fájljainkba kell beleírnunk.

 

Egész aritmetika

A let parancs argumentumaként aritmetikai kifejezés adható meg, amit a shell long integerekből álló aritmetikai kifejezésként értékel ki. Az alábbiakban, a kiértékelés szempontjából csökkenő precedencia sorrendben a használható operátorokat soroljuk fel.

-

minus jel (kötőjel)

!

logikai negálás

* / %

szorzás, osztás, maradék képzés

+-

Összeadás, kivonás

<= >= <>

Összehasonlítás

== !=

azonosság, különbség

=

Értékadás

A műveletek végrehajtásának sorrendjét zárójelekkel lehet átrendezni. Szintaktikusan ez a gömbölyű "()" zárójelpárt jelenti.

Amikor a < vagy a > jelet használjuk, a kifejezést idézőjelek közé kell tennünk, hogy a shell ne tévessze össze az I/O átirányítással.

A let szócskát helyettesíthetjük a "(())" zárójelpárral.

Nézzünk példákat. A let argumentumaként kapott értékadásban a műveleti jeleket valóban műveleti jelként fogja fel.

$ a=11+1

$ echo $a

11+1

$ let a=11+1

$ echo $a

12

$

Idézzük fel, hogy Bourne shell-ben egy ilyen értékadást hogyan tudnánk elvégezni!

Az alábbi, tartalmában már ismerős, ébresztőóra script Korn shell alatt így írható meg:

 

$ cat vekker

let a=`date | cut -c12-13`

let c=`date | cut -c15-16`

until ((a==$1))&&((c>=$2))

do

sleep 50

let a=`date | cut -c12-13`

let c=`date | cut -c15-16`

done

echo brrr

$

A job fogalma

A job nem más mint egy parancssor végrehajtás közben a maga teljes környezetével. Legegyszerűbb esetben ez egy parancs végrehajtása, de egy jobnak számít egy csőhálózat is, mint az alábbi példában:

$ ls -l|grep '^d'|sort>katalogusok

Információt a rendszerben nyilvántartott munkákról a jobs paranccsal lehet kérni, ami válaszként a jobszámot (szögletes zárójelben), a job állapotot, egy + vagy - jelet, valamint az eredeti utasítássort adja. A jobszám egy egész szám, amit a rendszer oszt ki. A job állapot az alábbi három érték egyike: Running, Done, Stopped, azaz futó, befejeződött és leállított A + jel az aktuális, a - jel a megelőzően indított munkát jelöli.

Stopped állapotba a rendszer suspend karakterével lehet a munkát tenni. A suspend karakter az stty paranccsal állítható. A szokásos beállítás:

$ stty susp Ctrl-z

A terminál használata és egy job futása között nem kell szoros kapcsolatnak lenni, például egy hosszan futó parancsnak nem kell az egész futás alatt a terminált foglalni. Erre megoldás a már a Bourne shell-ből ismert háttérben futtatás, amit a parancs utáni & jellel kezdeményezhetünk. A shell midőn a háttérbe teszi a munkát, visszaírja a jobszámot, szögletes zárójelben, és a folyamatszámot. A későbbiekben erre a munkára %jobszám-mal lehet hivatkozni. Az fg paranccsal előtérbe hozhatjuk; az előtérben futó jobot pedig a susped (rendszerint CTRL-Z) karakterrel felfüggeszthetjük, majd az fg, illetve bg paranccsal az előtérben, illetve a háttérben tovább futtathatjuk. Amennyiben lefutása előtt kívánjuk a munkát befejezni, akkor a kill paranccsal megölhetjük. Ha a monitor shell opció be van kapcsolva, a háttérben futó, de normálisan befejeződő parancs valami hasonló állapotüzenetet küldi a képernyőre:

[1]+Done du|sort>diskus&

Az utasítássor editálása, history használata

Gyakran tapasztaljuk, hogy azok akik DOS felhasználok is egyben, hiányolják, hogy a UNIX-ban nem lehet előhívni már egyszer végrehajtott utasításokat, az utasítássort minden esetben újra kell írni. Ez a szomorú tapasztalat igaz a Bourne shell-re, de nem igaz a Korn shell-re.

Amennyiben a shell opciókat megfelelően állítjuk be, úgy az utasítássor editálható. A shell opciókat természetesen akár interaktiven akár a .profile-unkból vagy a $ENV állományból beállíthatjuk, az alábbi módok egyikével.

$ set -o vi

Másik lehetőség, ha a VISUAL vagy az EDITOR változóknak vi, gmacs, vagy emacs értéket adva a megfelelő editort tesszük soreditorrá. Az EDITOR értékének vizsgálatára csak akkor kerül sor, ha VISUAL nincs beállítva.

VISUAL=vi

export VISUAL

illetve

EDITOR=gmacs

export EDITOR

A HISTFILE és a HISTSIZE változók tartalmazzák a parancstörténetet leíró állomány nevét, és utasítássorokban számlált méretét. Amennyiben másképp nem rendelkezünk, a history fájl neve .sh_history, hossza pedig 128 parancssor. Ebben az állományban mozoghatunk az editorok egyikével, ahogy azt már leírtuk, valamint az fc (fix command) paranccsal. A parancsot természetesen ebben az esetben is a parancsokat tartalmazó manuálban részletezzük, most csak néhány példa következik.

$ fc -l 20 22

20 pwd

21 ls -l

22 ps

$

A -l opció listázza a parancsfájl kijelölt részletét. A history szó egy szokásos alias az fc -l karaktersorozatra.

A -e opcióval az alkalmas editort választjuk ki, amivel a history megadott sorait editáljuk. Az így kialakított parancssorozatot el lehet tenni egy állományba, de függvényként végre is hajtódik és bekerül a historyba.

$ fc -e vi 20 22

Az alábbiakban az fc -e - parancs magában álló mínuszjele a kijelölt editorra vonatkozik. Az argumentum értelmezése a következő: a feljegyzett parancsokban visszafelé haladva, keresse meg az első olyan echo utasítást ahol a paraméterekben megtalálja a dio szócskát, azt cserélje ki mogyorora. Ezután kiírja az utasítást majd végrehajtja.

$ echo De jo a dio

De jo a dio

$ fc -e - dio=mogyoro echo

echo De jo a mogyoro

De jo a mogyoro

$

A historyt el lehet érni a már említett editorok használatával. Az alábbiakban a vi használatáról ejtünk néhány szót. A parancssorban egy esc karaktert ütve máris editor parancs módban vagyunk. Itt használhatjuk a vi editorból ismert kurzor mozgató billentyűket, parancsmódban kiadható parancsokat (pl.: a,i stb.). Pusztán azt kell tudnunk, hogy a mutató fölfele illetve lefele mozgatásával az előző parancssorokat tartalmazó állományban mozgunk, azok megjelennek a képernyőn, és javítgathatók, úgy ahogy azt leírtuk. A return gomb leütése után az így kialakított parancssort próbálja a shell végrehajtani.

 

Tartalomjegyzék