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:
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.
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.
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 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?
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.
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 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-zA 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.