A Bash programozási nyelv

Utasítások, vezérlési szerkezetek

Szekvencia

A bash-ben a szekvencia fogalma kétféle módon értendő: az egyik lehetőség, hogy azonos soron belül az utasításokat ";"-vel elválasztva írjuk egymás után - ezesetben a ";" nem utasítás lezáró, hanem utasítás elválasztó karakternek minősül.
A másik lehetőség, hogy az utasítások egymás utáni sokrokba írjuk, így a shell értelmezője ezeket külön utasításként kezeli.
Tehát vagy

echo "Hello"; read a

vagy

echo "Hello" read a

Test utasítás, logikai kifejezések

Korábbi leckében láttuk már, egy parancs kiadásakor a lefutás után lekérdezhető, hogy a kiadott parancsunknak mi lesz a visszatérési értéke. Egész pontosan a $? változó fogja tartalmazni a visszatérési értéket. Az elágazás utasítások, ciklus utasítások a korábban lefutott parancsok visszatérési értékét, mint logikai értéket vizsgálja és az értéknek megfelelő tevékenységet hajtja végre. Ha az érték 0, akkor az logikai igaz, ha 1 vagy nem nulla, akkor azt logikai hamis értékként használja a megfelelő utasítás. Az alábbi példák tesztelik a $? értékét. Megszerkesztünk egy hamis névre hallgató szkript programot, látható a lényegi utasítása az exit 1, ami azt jelenti, hogy az 1 visszatérési értéket adva lépjen ki a program. Ezen kívül az is látható, hogy egy egyszerű parancs, mint például az echo parancs is ad visszatérési értéket! (Minden parancsnak van visszatérési értéke!) Természetesen az echo parancs elvégzi a kiírási feladatot, majd a sikeres munka eredményeként logikai igaz értéket ad vissza (0). Általában elmondható, hogy egy parancs ha rendben elvégzi a feladatát, szűr valamit, összead két elemet, rendez stb, akkor logikai igaz értéket ad vissza (0), míg ha a parancs nem szűr ki egy sort sem (grep), üres sort olvas be, nem tudja rendesen elvégezni a feladatát, akkor hamis értéket (1) ad vissza. Látható, hogy nincs katalin-t tartalmazó sor, a grep sikertelen, a visszaadott érték hamis (1), míg viki-t tartalmazó sor van, így ennek szűrése sikeres, a visszaadott érték 0.

$> cat osztaly zoli 3 4 2 5 4 feri 2 3 4 5 3 4 juli 4 1 2 3 3 viki 3 5 4 2 5 5 $> cat osztaly|grep katalin $> echo $? 1 $> cat osztaly|grep viki viki 3 5 4 2 5 5 $> echo $? 0

Leszűrhetjük a fenti példákból, hogy logikai értéket (0-igaz, 1-hamis) valójában majd minden program szolgáltat futása eredményeként, de szkript programok elágazásához, ciklus készítéséhez szükséges logikai kifejezéseket ezek mégsem helyettesítik.

Tudjuk, a shell mindent szöveges formában kezel, valójában csak a szövegek egymás után írása az egyetlen művelet ebben a világban, így a logikai kifejezések klasszikus használatára sincs mód. Az i<5, j==6 formájú összehasonlítások, melyek majd minden programozási környezetben hasonló módon írhatók, ebben a világban nem használhatók. Ezeknek a lehetőségnek viszont minden környezetben helyük van, így ha nincs a klasszikus forma, akkor van helyette a shell nyújtotta test parancs. Bármely szkriptben a logikai ellenőrzéshez a test parancsot tudjuk használni. Segítségével rengeteg dolgot tudunk ellenőrizni, legyen szó értékekről, szövegről, fájlokról. Amikor Nem-interaktív a program, az alkalmazás képes lesz a szituációnak megfelelően döntéseket hozni, képesek leszünk döntésre kényszeríteni. Amikor pedig interaktív a program, használhatjuk akár navigációra, vagy ugyanúgy félig-meddig automatizált működésre. A test beépített parancs 0 (igaz) vagy 1 (hamis) értéket ad vissza a paraméterek értékétől/értékeitől függően. A parancs használatakor lehetőségünk van a [ ] jeleket is igénybe venni. Ezek a jelek a test szócska kiírását helyettesítik, segítségével majdnem úgy néz ki egy parancs mint egy klasszikus logikai kifejezés. A visszaadott értéket a '$?' kiíratásával is tudjuk ellenőrizni.
Lássunk a részletes paraméterek ismerete nélkül néhány egyszerű példát

$> gyumolcs=alma $> test $gyumolcs = barack $> echo $? 1 $> test $gyumolcs = alma $> echo $? 0 $> [ $gyumolcs != barack ] $> echo $? 0

Látható a fenti néhány példában a test utasítás használata. Szöveges összehasonlító műveleti jelek az egyenlőség jel(=) az azonosság vizsgálatot jelenti, míg a != jel a nem azonos jelentéssel bír. A $gyumolcs = barack vizsgálatnál így azt kérdeztük, hogy a csapat változó tartalma (alma) a barack-e? Persze, hogy nem az, ezért az összehasonlítás eredménye hamis, ami $? változóban az 1-t jelenti. Az utolsó példában a test utasítás a [] jelekkel helyettesítettük. Ehhez csak annyi megjegyzést kell hozzátenni, hogy amint példából is látszik a [ és ] jel közé kell írni a paramétereket. A test utasításnál az így megadott adatok, összehasonlító jel, mind parancs paraméterek, így azokat helyköz választja el egymástól. Parancsok logikai eredményétől, sikerességétől függően; lehetőségünk van két vagy több parancsot összekapcsolni, parancsokat az előző parancs eredményétől függően végrehajtani. Erre a &&, illetve a || parancsjelek használata ad lehetőséget.
Ha egy parancs sikeresen lefut, akkor a && jellel utána írt parancs is le fog futni. Ha egy parancs sikertelen, akkor a || jellel utána írt parancs fog végrehajtódni. Ez a parancs összekapcsolási mód, valójában egy elágazás utasítás helyettesít.
Lássunk a használatára egy egyszerű példát.

$> gyumolcs=alma $> [ $gyumolcs = alma ] && echo Nyertem! Nyertem! $> [$gyumolcs = alma ] || echo Most is nyertem! Most is nyertem!

Az első test utasításban ([ $gyumolcs = alma ] ) igaz értéket kapok, így az echo parancs a && jelek után végrehajtódik. A második test utasításban nem igaz, hogy a gyumolcs nem az alma, így hamis értéket ad a test, azaz a || jelek utáni parancs lefut. Természetesen, ezeket a lehetőségeket össze is kapcsolhatjuk, ahogy a következő példa is mutatja.

$> [ $gyumolcs = alma ] && echo Nyertem! || echo vesztettem Nyertem! $> [ $gyumolcs = alma ] && echo Nyertem! || echo vesztettem vesztettem

Fájlok, könyvtárak létezésének vizsgálatát is elvégezhetjük a test parancs segítségével. Az alábbi példában, a [ ] jeleket használva megnézzük, hogy egy fájl létezik-e?

$> [ -f /home/bash/adat.txt ] $> echo $? 1

Az utolsó példában pedig a prognyelvek mappa létét nézetjük meg, hogy az létezik-e.

$> [ -d /home/bash/prognyelvek ] $> echo $? 0

A test utasításról a korábban taglalt műveleti jelek használhatók a megfelelő összehasonlítások elvégzéséhez:

Műveleti jel Angol jelentés Magyar jelentés
-eq equal egyenlő
-ne not equal nem egyenlő
-lt less than kevesebb mint
-le less than or equal kevesebb mint vagy egyenlő
-gt greater than nagyobb mint
-ge greater than or equal nagyobb mint vagy egyenlő
= equal string két szöveg megegyezik
!= not equal string két szöveg nem egyezik
! not a kifejezés eredményének negáltja

A test parancsról is az aktuális rendszerben a man test parancs kiadásával olvasható a teljes dokumentáció.

Elágazás

Elágazásokat leginkább kétféle csoportba sorolhatjuk. Az egyik csoport a kétirányú elágazások, a másik csoport a többirányú elágazások, melynél egy változó több különböző értékétől függően több lehetőség közül az egyiket választja.

Kétirányú elágazás

Ezt akkor használjuk, ha egy kódrészletet csak akkor akarunk futtatni, ha az if feltétel igaz. A szintaxisa:

if feltétel then parancsok fi

Az if utáni feltételt gyakran írjuk a következő sorba, ezáltal a forma változhat:

if feltétel then parancsok fi

Ennek az írásmódnak egy előnye biztosan van a korábbi formához képest, biztosan sugallja azt, hogy a feltétel nem feltétlenül egy utasítást jelent. Több utasítás is írható feltételként, és az utolsó eredményét fogja vizsgálni a shell, ha az igaz értéket ad, akkor a then ág utasításait is végre fogja hajtani!

if feltétel; then parancsok fi

Még egy másik írásmód az, ha két sor helyett egyben írjuk az if és then utasításokat. Ekkor közéjük pontosvesszőt (;) kell tenni, hiszen ez az utasításokat elválasztó jel.
Ha viszont tényleg kétirányba akarunk ágazni, és a ha-akkor-különben lehetőséget szeretnék megvalósítani, akkor így fog kinézni:

if feltétel then parancsok else más parancsok fi

Természetesen az else ágban megint elágazhatunk két irányba, és mivel gyakran előfordul ilyen, ezért egyszerűbb, ha rövidebben, az elif parancsot használjuk.

if feltétel then parancsok elif más parancsok else még más parancsok fi

FONTOS! A feltétel valójában bármilyen kifejezés, parancs lehet, csak a visszatérési értékét vizsgálja a parancsértelmező, ahol a 0 jelenti az igazat, az 1 pedig a hamisat.

Többirányú elágazás

Többirányú elágazásra a case szerkezetet szoktuk használni, amelyet akkor célszerű választani, ha különböző lehetőségek bekövetkezésekor különböző kódokat szeretnénk futtatni. Bizonyos feladatok megvalósítása egyszerűbb ezzel, mint az if-fel, pl. egy menürendszer kódolása. A feltételnél megadunk egy változót, és ezen változó értékének függvényében fog elágazni a case.
A parancs szintaxisa:

case $valtozo in feltétel1) parancsok ;; feltétel2) parancsok ;; feltételN) parancsok ;; *) parancs ;; esac

A case elágazás sorban megvizsgálja, hogy a $valtozo-ban megvan-e patternx érték; ha igen akkor az utána lévő parancsot hajtja végre, ha nincs, megy tovább. A *) (mint bármi) az utolsó helyen az egyéb esetet hivatott megvalósítani. Minden pattern kezdetű blokkot ;;-vel választunk el, a case utasítás záró elemeként pedig az esac utasítást használjuk.
Lehetőségünk van több értéket is vizsgálni egy blokkban a függőleges vonal (|) karakter segítségével:

case $val in pat1) echo "p1";; pat2 | pat3 | pat4) echo "p2-4";; pat5) echo "p5";; *) ;; esac

Az elágazás minta konstans értékei nem reguláris kifejezések, inkább a fájlnév mintaillesztéshez hasonlítanak, a [] a választható karakterek jelölésére szolgál, a * jelentése minden, a ? egy tetszőleges karakter.

Ciklus

A ciklusokra is van megfelelő vezérlési szerkezet a shell szkriptes környezetben. Háromféle ciklust különböztetünk meg: a számlálós ciklust, amit akkor használunk, amikor előre tudjuk, hogy valaminek hányszor kell lefutnia; az elöltesztelő ciklust, amikor egy feltételtől függően indul el a ciklusmag, és lehet, hogy egyszer sem fut le, illetve a hátultesztelő ciklust, amiben a ciklusmag egyszer biztosan lefut, és a végén egy kilépési feltétel dönti el, hogy kiléphet-e a ciklusból a program.

Számlálásos ciklus

A számlálós ciklusnál tehát előre ismert, hogy hányszor kell lefutnia. Ebből is van kétféle lehetőség. Ha egy ciklusváltozónk van, és annak az értékét állítjuk, akkor a következő szintaxist kell használni:

for ((i=1; i<=10; i++)) do echo "$i. lépés" done

Megjegyzés! A for ciklusnak ehhez a változatához végül egy megjegyzést kell tenni. Ez a C stílusú for ciklus a bash shell környezetben használható, a normál Bourne shell, sh nem ismeri. Emiatt a szkript programokban gyakran kerülik is a használatát!
A for ciklus klasszikus alakja olyan, hogy első ránézésre nem is tűnik nagyon szerencsésnek. Egy felsorolásban az in kulcsszó után, kell megadni a ciklusváltozó értékeit. Ezeket az értékeket vagy közvetlenül a ciklusba írjuk, vagy egy változóba rakjuk. Előbbi esetben a ciklus formája a következő:

for elem in auto motor traktor do echo $elem done

Ekkor az elem ciklusváltozó rendre felveszi az auto, a motor, majd a traktor értékeket, és így fog legfutni a do és done közötti ciklusmag. Ennek eredményeként a kimenet 3 sor lesz, amely az auto, motor, és traktor sorokból fog állni.
Ha egy változóba rakjuk az elemeket, ezt gyakran egy tömb feldolgozásának is mondjuk (a helyközzel elválasztott elemekre, mint tömbelemre is tekinthetünk), ekkor a változó minden elemét szeretnénk feldolgozni, akkor a következő szintaxisnak megfelelő kódot kell módosítani a feladatnak megfelelően:

elemek=auto motor traktor for elem in $elemek do echo $elem done

A fenti példákból látható, hogy a for ciklus egy adatsorból veszi a ciklusváltozó adatait, és ahány adat van, annyiszor lefut a ciklus. A for ciklushoz gyakran társítjuk a számlálásos ciklus elnevezést is, így mi is szeretnénk ilyen jellegű ciklust készíteni. Ehhez a seq parancsot találjuk a shell környezetünkben, ami a számlálás feladatát végzi el, és ennek a programnak az eredményét, a számokat adjuk a for ciklus paraméterének. Ha kiadjuk a man seq parancsot a terminálon, akkor a manuál eleje a következőt fogja adni:

NAME seq - print a sequence of numbers SYNOPSIS seq [OPTION]... LAST seq [OPTION]... FIRST LAST seq [OPTION]... FIRST INCREMENT LAST DESCRIPTION Print numbers from FIRST to LAST, in steps of INCREMENT.

A seq parancs egy szám szekvenciát hoz létre, amelynek 3 féle paraméterezése lehet:

Elöltesztelő ciklus

Ezt akkor használjuk, ha egy bizonyos feltételig akarjuk futtatni a ciklust.
Az elöltesztelő ciklus szintaxisa:

while [ feltétel ] do ciklusmag done

A while-ciklus tipikus példája egy állomány beolvasása, hiszen ott addig fut a ciklus, ameddig tud belőle olvasni, vagy egy könyvtár listázása. Ha üres a fájl, akkor egyszer sem fog lefutni a ciklusmag.
A feltétel bármilyen aritmetikai, szöveges ellenőrzés lehetséges, amelyeket a test parancsnál is használhatunk.
A C nyelvekhez hasonlóan a ciklusból break kulcsszóval léphetünk ki és continue-val ugorhatunk a ciklus elejére.

Hátultesztelő ciklus

A hátultesztelő ciklus működése eltérő, mivel a feltételvizsgálat a ciklusmag után történik, ezért a hátultesztelő ciklus legalább egyszer mindenképpen lefut. A konkrét programnyelvi megvalósítástól függ, hogy a hátultesztelő ciklusban a folytatás vagy a kilépés feltételét kell-e megadni. A ciklus az első esetben addig fut, amíg a ciklusvégben megadott feltétel igaz (ennek tipikus kulcsszava a while), a másik esetben pedig addig, amíg igazzá nem válik (tipikus kulcsszava az until). A Unix világ shell scriptjében ez a klasszikus hátul tesztelő ciklus nincs, van helyette egy fordított while ciklus.
Az until ciklus felépítése a shell szkriptekben teljesen megegyezik a korábban megismert while ciklussal, két apró eltéréssel:

Az until ciklus alakja a következő:

until parancsok lehetnek itt is [ feltétel ] do ciklusmag done

Ez addig fog futni, amíg a feltétel nem igaz, avagy mondhatjuk, hogy akkor fog kilépni, ha igazzá válik.