Perl alapjai IV. - Tömbök, hashek, elágazások, ciklusok és szubrutinok
A cikksorozat mai cikkében továbbhaladunk a nyelv megismerésével, az elején a tömbökkel, hashekkel végezhető műveleteket vesszük át, majd az elágazások, ciklusszervezési lehetőségek, s legvégül a szubrutinok következnek. Mára már nem jutott tevés kép, egy teve megtekintéséhez egy melegebb éghajlatra való utazást, vagy valamely közeli állatkertbe történő kiugrást tudom javasolni (vagy esetleg a Perl weblap felkeresését?). Ezen alkalommal is bemutatok egy rövid programot, mely előre mutat egy kicsit, de nagyrészt épít az eddig megismert lehetőségekre.
Nézzük végig az idevágó parancsokat!Az első példa levágja a tömb elemeit az ötödik elemtől kezdve. A második példa két elemet vág le az ötödik elemtől, a harmadik példa pedig kiveszi az ötödik és a hatodik elemet, és a helyükre beírja, hogy 'ötödik' és 'hatodik'. Mind a három tömb azokat az elemeket fogja tartalmazni, amelyek el lettek távolítva a tömbből.
Ennek az adatszerkezetnek a használatához a push és a pop parancsokat vehetjük igénybe. A push egy tömbhöz ad hozzá egy skalárt, a pop pedig egy tömbből vesz ki egyet (véglegesen eltávolítva onnan):Az első sor az alapeset, legtöbbször így használjuk az eljárást. A második sor mutatja be, hogy hogyan lehet megváltoztatni a feltételt, a harmadikban pedig egy példát látunk arra, hogy hogyan lehet számokat rendezni. A $a és a $b változók tartalmazzák a két összehasonlítandó értéket, ezeknek nem kell értéket adnunk. Értelemszerűen, ha felcseréljük a $a-t és a $b-t, akkor csökkenő sorrendben rendezett tömböt kapunk eredményül. A második és a harmadik példában valójában egy szubrutin foglal helyet az első paraméter helyén, ehelyett egy szubrutin nevét is lehet használni.
Adott tömb esetén, amennyiben szükségünk van a sorrend megfordítására, akkor a reverse eljárást használhatjuk. Ez a paraméteréül megadott tömböt fordított sorrendben adja vissza. Amennyiben rendezünk és utána lenne szükségünk a fordított sorrendre, akkor memóriahasználati szempontból inkább ne ezt használjuk, rendezzünk eleve fordított sorrendben (a két változó cseréjével: 'sort ({$b cmp $a}, @redezetlen)' ugyanaz lesz, mint a reverse sort ({$a cmp $b}, @redezetlen), csak tovább tart).Az első példa egyértelmű, megfordítja a tömb elemeinek sorrendjét. Kérdés viszont, hogy mit csinál a második, hiszen nincs igazán értelme sorrendről beszélni a hash-ek esetén (a fordító logikája szerinti sorrendben tárolódnak a memóriában)? Az értékadáskor már bemutattam, hogy egy hash-nek egy tömböt is lehet értékül adni, ekkor páronként kerülnek be a hash-be a tömb elemei, az elsők (páratlan sorszámúak) a hash kulcsai, a másodikak (párosak) pedig ezek értékei lesznek. Ha megfordítjuk egy hash sorrendjét, akkor úgy viselkedik, mintha egy ilyen tömbnek a sorrendjét fordítanánk meg, azaz az értékek kulccsá válnak és fordítva. Nagyon hasznos trükk.Igen, ezek a függvények jól használhatóak, de mit tegyünk, ha mi pont a fordítottját szeretnénk tenni a dolognak, például hozzáadni egy újsort vagy pedig egy teljesen más feladatunk akad? Erre van a map parancs. Két paramétert kell neki megadni, az első egy parancs - amit tenni szeretnénk az egyes elemekkel -, a parancs eredménye lesz az adott elem új tartalma, a második a tömb. Az aktuális elem $_ változóba kerül bele. Íme pár példa:Mint látható ez a parancs már nem a tömbön magán dolgozik, hanem az eredménye egy új tömbbe kerül bele. Az első példa idézőjelbe teszi minden elemét a tömbnek, a második pedig a tömb minden elemét négyzetre emeli. Az utolsó példa gyakorlatilag a chomp megvalósítása, persze lehetett volna hatékonyabban is írni, de törekedtem az érthetőségre, hiszen a feltételes utasításokat még nem mutattam be. Röviden annyit csinál, hogyha az utolsó karaktere az adott elemnek újsor, akkor a kérdőjel előtti részt adja vissza, azaz az utolsó karakter levágásra kerül, különben pedig a kérdőjel utánit, azaz az eredeti értéket.
Ebben a blokkban még a grep utasítás maradt hátra. Ezzel egy kiválogatást végezhetünk, azaz egy általunk megadott feltétel segítségével megadhatjuk, hogy a visszaadott tömbben szerepeljen-e az adott elem, vagy nem.Az első példa a 'szamok' tömbből visszaadja a háromjegyű számokat (feltéve, hogy egész számokról beszélünk). A második segítségével, amennyiben a 'sorok' tömbbe egy állomány sorai vannak betöltve, akkor kiszűri belőle azokat, melyek nem '#' karakterrel kezdődnek.A példa kiírja a hash elemeit kulcs=érték formában, külön sorokban.
Perl-ben egyetlen lehetőség áll rendelkezésünkre elágazások használatára, ez az if-es szerkezet, azonban ezt olyan sokféleképpen és rugalmasan használhatjuk, hogy nincs is más lehetőségre szükségünk. A legáltalánosabb formája a következő:Mind az elsif és az utána következő blokk, mind az else és az utána következő blokk tetszés szerint elhagyható. Elsif blokkból többet is használhatunk. A fenti program működése tehát a következő: ha feltétel1 igaz, akkor a hozzátartozó blokk végrehajtódik, különben ha a feltétel2 igaz, akkor a második blokk végrehajtódik, különben pedig az else-hez tartozó blokk hajtódik végre. Lássunk a használatára példákat:A fenti példák remélhetőleg egyértelműek. A feltételeknél a műveleteknél megismert összehasonlításokat alkalmaztam. Az if szó helyett további lehetőség az unless szó használata, amely gyakorlatilag az ellenkezőjét fogja tenni az if-nek, akkor hajtódik végre az if-hez tartozó utasításblokk, ha a kifejezés nem igaz. Elsunless utasítás nem létezik. A használata ezenkívül teljesen megegyezik az if-ével:Amennyiben az utasításblokkunk csak egy utasításból áll, rendelkezésünkre áll egy további lehetőség is:Ekkor az utasítás csak akkor fog végrehajtódni, ha a feltétel igaz (if), illetve hamis (unless). Erre a lehetőségre pár példa:Az első üzenet akkor jelenik meg, hogyha a szam változó értéke nagyobb, mint 9999, a második pedig akkor, ha a hiba nevű változó értéke nem 0. Természetesen ezeknek az utasításoknak az "egymásba ágyazása", azaz feltételes utasítás használata egy feltételes utasítás utasításblokkjában minden további nélkül lehetséges.
Az egyik legegyszerűbb ciklusszervezési lehetőség a while ciklus. A szintaktikája a következő:Az utasításblokk amíg a feltétel igaz, addig ismétlődik (másik oldalól megközelítve, amíg hamissá nem válik). A példa (ami a négyjegyű egészeket írja ki):A while-hoz hasonló ciklusszervezési lehetőségünk az until, ami ugyanúgy működik, mint a while, kivéve, hogy a "ciklusmagot", azaz az utasításblokkban szereplő utasításokat addig ismétli, amíg feltétel igazzá nem válik, azaz ameddig hamis. Ezzel megoldva a példánk a következőképpen alakul:Az if és az unless szerkezetekhez hasonlóan mind a while, mind az until szerkezet lehetőséget ad arra, hogy a feltételt egy utasítás után írjuk:A do függvény segítségével utasításblokkot is használhatunk az egyetlen utasítás helyett. Így egyetlen különbség lesz a "sima" while és until ciklusokhoz képest: a ciklusmag akkor is végre fog hajtódni egyszer, ha a feltétel nem igaz (while), illetve hamis (until) (ez az előző példára is igaz!). Ilyenkor a ciklusmag elé a do szót kell írnunk. A példa do-while-al:Illetve do-until-lal:A következő ciklusszervezési lehetőségünk a for ciklus. Nagyon rugalmas szerkezet, rendkívül széleskörűen felhasználható. A következőképpen lehet használni:Az utasítás1 utasítás az egész ciklus elején hajtódik végre, majd végrehajtódnak az úgynevezett ciklusmag helyén szereplő utasítások, s utána az utasítás2 utasítás, majd megint a ciklusmag és megint az utasítás2, és így tovább, amíg a feltétel igaz, és hamissá nem lesz. Ez így elsőre biztosan bonyolultan hangzik, lássuk, hogy hogyan oldható meg a feladatunk ezzel a ciklusszervezési módszerrel:Első alkalommal értéket adunk a szam változónak, majd amíg nem érjük el a 10000-et, addig kiírjuk a szam változó értékét és növeljük eggyel. A zárójelben lévő részek tetszés szerint elhagyhatóak, vagy akár (a feltétel kivételével) több is írható belőlük, vesszővel elválasztva:A ciklusok használatakor felmerülhet az az igény, hogy a ciklusmag közepén valamely feltétel esetén ki szeretnénk lépni a ciklusmagból. Erre háromfajta lehetőségünk van, a next, a last és a redo utasítás. A next segítségével az utasításblokk végére ugorhatunk, a redo segítségével az elejére (így ugyanazokkal az értékekkel mégegyszer végrehajtódik a ciklusmag), a last pedig befejezi a ciklus végrehajtását és kilép belőle. A következő példák mutatják be ezeknek az utasításoknak a használatát:A ciklus addig ismétlődik, míg tud új sort beolvasni. Ha beolvasott egy sort, akkor a tartalma szerint a következők történhetnek: 'kilép' esetén kilép a ciklusból, 'üdv' és 'hello' esetén visszaír egy 'Hello!'-t, a többi esetben pedig azt írja ki, hogy 'Nem értem.'. A 'Hello!' kiírása után nem íródik ki a 'Nem értem.', mivel a next utasítást használtuk, az 'üdv' pedig megváltoztatja a line változó értékét és a redo-val újra feldolgoztatja azt, ezért kapjuk a 'Hello!' üzenetet.
Ennek a résznek a végére a rendkívül jól használható foreach utasítás maradt. Ez veszi egy tömb összes elemét és mindegyikhez végrehajtja a hozzá tartozó utasításblokkot. Egy példa a használatára:Mint látható, az aktuális elem az '_' változóban áll rendelkezésünkre. Ezt már használtuk korábban (grep-nél, map-nél) és rejtett változónak hívjuk. A fenti ciklusszervezési módszerekkel rendkívül változatos és elegánsan használható ciklusszervezési eszközökhöz juttat minket a Perl, melyek kevés nyelvben használhatóak ilyen kényelmesen és változatosan.Ezt a következőkre tudjuk leegyszerűsíteni:Ez azon az előnyön kívül, hogy nem kell 4-szer beírni a kiíró sort, azzal az előnnyel is jár, hogy ha a későbbiekben módosítani kell a programot, hogy az eggyel nagyobb számokat írja ki, akkor nem négy helyen kell azt megtennünk. Amennyiben elképzeljük, hogy 20-30 soros az ismétlendő rész (ennél sokkal-sokkal hosszabb megoldások is vannak), még érthetőbb lesz a probléma.
De nézzük végig, hogy mit is csinál a fenti program, mit is jelentenek az egyes sorai. A
Mint látható volt, az eljárásunk a 'szam' értékét használja. Felmerülhet bennünk a kérdés, hogy nem lehetne-e még egyszerűbben megoldani a problémánkat? Természetesen lehet, paraméterátadással. Ilyen paraméterátadást használunk akkor is, mikor leírjuk, hogyA változás annyi, hogy az eljárásunk kibővült egy sorral, ahol a 'szam' változónak értéket adunk. Az eljárás számára átadott változók az '_' nevű tömbbe (@_) kerülnek bele (már megint egy rejtett változó), s abból vesszük ki őket ebben a sorban.
Bővítsük tovább tudásunkat a függvényekkel, azaz az olyan metódusokkal, ahol egy értéket kapunk vissza. Íme egy példa ennek megvalósítására:A példában a köbre emelő függvényt valósítottuk meg. A függvény visszatérési értékét a 'return' utasítás segítségével határozhatjuk meg, az eljárás egyszerűen veszi az első paramétert (az '_' tömb első eleme: $_[0]), és visszaadja annak a köbét. A függvény visszatérési értéke ha nem adunk meg 'return'-nel semmit, akkor az utolsó kifejezés értékét veszi fel, azaz írhattuk volna a következőket is a függvényünkben:Hogyan oldhatjuk meg, ha több paramétert szeretnénk visszaadni? Kétféle módszerrel. Az egyik, amely talán magától értetődik: a visszatérési értékünk legyen egy tömb, melynek elemei az általunk visszaadandó értékek:A függvény a páratlanszámokat adja vissza a két paraméter között (paraméterként egész számokat kell megadni, hogy helyesen működjön a függvény). A ret tömbbe kerülnek bele a számok, s ahogy az előbb is, itt is a return paranccsal térünk vissza az függvény végén.
A másik megoldás a cím szerint átadott paraméterek használatával történik. Perlben is kétféleképpen használhatjuk a paramétereket, az egyik, mikor létrehozunk egy új változót és azt használjuk, módosítjuk (ahogy a köbös példa kivételével eddig tettük). Ez az érték szerinti átadás megfelelője. A másik módszer, mikor az '_' tömb elemeire közvetlenül hivatkozunk, ha megváltoztatjuk ezek értékét, akkor a "külső" változó értéke is módosul (tehát Perlben valójában csak cím szerinti átadás történik). Lássunk erre is egy példát:Ahogy múltkor sem, most sem mutatom be részletekbe menően a programot, ezt az olvasóra bízom: próbálgassa, egészítse ki, írja át - ezzel lehet tanulni a legtöbbet. Röviden annyit, hogy a program elején megnyitjuk a naplófájlt, majd egy ciklusban végigmegyünk a sorain. Az a ronda dolog ott az elején egy reguláris (avagy mintaillesztő) kifejezés, erről remélhetőleg majd egyszer később még lesz szó a Weblabor hasábjain. Ezután egy hash-be pakoljuk a kifejezés által talált elemeket a sorból, majd feldolgozzuk. A program végül napokra bontott statisztikát fog nyújtani a találatokról, a különböző IP címekről, és a hivatkozó oldalakról (és a hivatkozások számáról).
Nos, már ennyi jutott a Perlből, legközelebb folytatjuk az ismerkedést.
■ A sorozatban megjelent
- Perl alapjai I. - A múlt és a jövő
- Perl alapjai II. - Fejlesztői környezet, alapok
- Perl alapjai III. - Műveletek
- Perl alapjai IV. - Tömbök, hashek, elágazások, ciklusok és szubrutinok
- Perl alapjai V. - Mintaillesztő kifejezések
- Perl alapjai VI. - Fájlkezelés
- Perl alapjai VII.- Fájlműveletek
- Perl alapjai VIII. - Könyvtárműveletek
- Perl alapjai IX. - Adatbáziskezelés
Műveletek tömbökkel, hash-ekkel
A tömbök, hash-ek használatához is találunk beépített parancsokat, s bár személy szerint én hiányolok pár lehetőséget (unió, metszet, komplementer), de így is jóval többet lehetőséget tartalmaz a nyelv, mint például a C. Ebből a szempontból a Perl 6-os nyelvi szinten majd számos lehetőséget kínál az összekapcsolás (junctions) segítségével, de arra még várnunk kell majd egy kicsit. Persze ez a jelenlegi verziókban sem lehetetlen (sőt, teljesen egyszerű), például a mai cikk végén levő kódban az egyedi értékeket válogatjuk ki és számoljuk meg, mikor a hivatkozó oldalakat figyeljük.Nézzük végig az idevágó parancsokat!
Kivágás, csere (splice)
Már láthattuk, hogy hogyan tudunk hozzáadni egy tömbhöz értékeket, de még nem tudjuk, hogy hogyan lehetne egyszerűen kicserélni, vagy levágni elemeket. Erre szolgál a splice parancs. Segítségével egy tömbből a megadott pozíciótól a megadott hosszig kivágja, vagy ha meg van adva egy lista, akkor kicseréli rá a tömb elemeit:
@tomb1=splice(@tomb,4);
@tomb2=splice(@tomb,4,2);
@tomb3=splice(@tomb,4,2,'ötödik','hatodik');
Verem (push, pop)
A verem egy nagyon széleskörűen alkalmazott adatszerkezet, általában a megvalósítása tömb segítségével történik. Röviden leírva a működését: hozzá lehet adni és ki lehet venni belőle elemeket, amit utoljára betettünk, azt tudjuk kivenni elsőre (Last In First Out - LIFO), másképpen fogalmazva, fordított sorrendben kapjuk vissza az elemeket, mint ahogy a szerkezetben elhelyeztük.Ennek az adatszerkezetnek a használatához a push és a pop parancsokat vehetjük igénybe. A push egy tömbhöz ad hozzá egy skalárt, a pop pedig egy tömbből vesz ki egyet (véglegesen eltávolítva onnan):
@verem=(1..10);
push(@verem,11);
push(@verem,12,13,14,15);
print pop(@verem);
Sor (shift, unshift)
A verem mellett a másik gyakran használt adatstruktúra a sor. Ezt is tömb segítségével valósíthatjuk meg, ennek a lényege, hogy amit elsőnek betettünk, azt vesszük ki elsőnek (First In First Out - FIFO). Használatához a shift és az unshift eljárások állnak rendelkezésünkre. Az unshift beszúr elemeket, a shift pedig kivesz:
@sor=(5..10);
unshift(@sor,4);
unshift(@sor,1,2,3);
print shift(@sor);
Sorrend megváltoztatása (sort, reverse)
Gyakran lehet szükségünk egy tömb elemeinek rendezésére. Erre szolgál a sort parancs, mely egy rendezett tömböt ad vissza (tehát nem helyben, magát a tömböt rendezi). A C nyelv qsort algoritmusával rendez, tehát viszonylag gyors. Alapesetben ASCII sorrend szerint hajtja végre a feladatát (tehát nem a számok értéke szerint!, ezért a 13 kisebb lesz, mint az 2, mert az 1 az kisebb, mint a 2), de az összehasonlító feltétel megváltoztatható, akár egy külön programrész, szubrutin írható rá - így elég összetett, több szempont szerinti rendezések valósíthatóak meg egy gyors algoritmusú rendezés segítségével.
@rendezett = sort(@rendezetlen);
@rendezett = sort({lc($a) cmp lc($b)} @rendezetlen);
@rendezett = sort({$a <=> $b} @rendezetlen);
Adott tömb esetén, amennyiben szükségünk van a sorrend megfordítására, akkor a reverse eljárást használhatjuk. Ez a paraméteréül megadott tömböt fordított sorrendben adja vissza. Amennyiben rendezünk és utána lenne szükségünk a fordított sorrendre, akkor memóriahasználati szempontból inkább ne ezt használjuk, rendezzünk eleve fordított sorrendben (a két változó cseréjével: 'sort ({$b cmp $a}, @redezetlen)' ugyanaz lesz, mint a reverse sort ({$a cmp $b}, @redezetlen), csak tovább tart).
@vissza = reverse(@elore);
%hash1 = reverse(%hash2);
Műveletek a tömb összes elemén (chomp, chop, map, grep)
A tömbök minden elemén végrehajtandó feladat elvégzése szintén gyakori feladat különböző programrészek, algoritmusok megvalósításánál. Természetesen számos módszer létezik, az egyik legkézenfekvőbb, hogy valamilyen ciklusszervezési módszerrel (lásd később) végighaladunk az egyes elemeken. Erre azonban vannak a Perl-nek elegánsabb megoldásai is. Például, mikor egy szöveges állományból betöltjük a sorokat egy tömbbe, akkor a sorvég jelek ott maradnak a sorok - azaz most már a tömb elemeinek végén. Ezt a problémát lehet megoldani a chop eljárás segítségével, melynek egyetlen paramétere van: a feldolgozandó tömb. Az utasítás helyben dolgozik, azaz az eredménye a megadott tömbön jelentkezik - levágja a sorok utolsó karakterét. Ennek az eljárásnak a "biztonságosabb" változata a chomp, ez csak akkor vágja le az utolsó elemet, amennyiben az újsor karakter, illetve Windows alatt ha újsor karakterpáros. Ezt a nyelv fejlődése során vezették be a felhasználók tapasztalataira épülő egyik javaslat hatására - hiszen többek között például akkor nem működik a chop, ha a file utolsó sorának végén már nincsen újsor karakter. A következő példák ezeknek a függvényeknek a használatát mutatják be (megjegyzendő, hogy tömb helyett egy skalár is átadható ezeknek a rutinoknak, ilyenkor az adott skaláron végzik tevékenységüket):
chop(@sorok); # a "sorok" elemeinek utolsó karakterét levágja
chomp(@sorok); # ez pedig a "biztonságosabb" változat
@ujtomb=map('"'.$_. '"',@egytomb);
@ujtomb=map($_**2,@egytomb);
@tomb=map(substr($_,-1) eq "\n"?substr($_,0,-1):$_,@tomb);
Ebben a blokkban még a grep utasítás maradt hátra. Ezzel egy kiválogatást végezhetünk, azaz egy általunk megadott feltétel segítségével megadhatjuk, hogy a visszaadott tömbben szerepeljen-e az adott elem, vagy nem.
@haromjegyu=grep($_>99 and $_<1000, @szamok);
@nemmegjegyzes=grep(substr($_,0,1) ne '#', @sorok);
Kulcs meglétének vizsgálata hash-ben (exists)
A végére maradtak a hash-ekkel végezhető műveletek. Hash-eket használva, szükségünk lehet arra, hogy megvizsgáljuk, definiálva van-e egy adott kulcs a hash-ben vagy nem? Erre szolgál az exists operátor, ami igaz értéket ad vissza, ha létezik, s hamisat, ha nem:
exists( $hash{'kulcs'} ); ='', ha nincs, =1, ha van
Hashek kulcsai, értékei (keys, values)
Tipikus feladat lehet, hogy egy hash minden elemére, vagy minden értékével szeretnénk tenni valamit, erre szolgálnak a keys és values operátorok. A keys a hash kulcsait adja vissza egy tömbben, a values pedig az egyes értékeket. A keys-re egy példa (a values ugyanígy működik):
print map( $_."=".$hash{$_}."\n", keys %hash );
Kulcs-érték párok (each)
Amennyiben szeretnénk egy hash elemein végigfutni és utána használni a továbbiakban valamire, akkor az each parancsot használhatjuk. Ez egy kételemű listát ad vissza, melyben a soron következő kulcs és érték értékek vannak benne. Ha a hash elemeinek végére értünk, akkor mindkét elem undefined lesz, majd kezdődik előröl.
($kulcs1,$ertek1)=each(%hash);
($kulcs2,$ertek2)=each(%hash);
($kulcs3,$ertek3)=each(%hash);
Elágazások
Egy programban legyen az bármilyen egyszerű is, nagyon valószínű, hogy lehetővé kell tennünk, hogy bizonyos feltételek teljesülése és nem teljesülése esetén más és más történjen. Erre szolgálnak a feltételes utasítások. A feltételes utasítások legalább egy feltételből, s legalább egy utasításblokkból állnak. A feltétel egy kifejezés lehet, melynek értéke vagy igaz, vagy hamis, az utasításblokk pedig egy olyan szerkezet mely (általában) több utasítást fog egybe. Az utasításblokkokat kapcsos-zárójelek közé írjuk, s a kapcsos-zárójelek akkor sem hagyhatóak el, ha egyetlen utasítást fognak közre (ellenben a Pascal begin-end-jével, vagy a C kapcsos-zárójeleivel).Perl-ben egyetlen lehetőség áll rendelkezésünkre elágazások használatára, ez az if-es szerkezet, azonban ezt olyan sokféleképpen és rugalmasan használhatjuk, hogy nincs is más lehetőségre szükségünk. A legáltalánosabb formája a következő:
if (feltétel1)
{
# ha feltétel1 igaz
utasítások;
}
elsif (feltétel2)
{
# ha feltétel1 nem igaz és feltétel2 igaz
utasítások;
}
else
{
# ha egyik feltétel sem igaz
utasítások;
}
if ($szam<10) { print "0".$szam } else { print $szam }
if ($parancs eq 'elore')
{
print "$ertek1 $ertek2 $ertek3\n";
}
elsif ($parancs eq 'hatra')
{
print "$ertek3 $ertek2 $ertek1\n";
}
elsif ($parancs eq 'elso')
{
print "$ertek1\n";
}
elsif ($parancs eq 'utolso')
{
print "$ertek3\n";
}
elsif ($parancs eq 'kozepso')
{
print "$ertek2\n";
}
else
{
print "Ismeretlen parancs!\n";
}
unless (feltétel1)
{
# ha feltétel1 nem igaz
utasítások;
}
elsif (feltétel2)
{
# ha feltétel1 igaz és feltétel2 igaz
utasítások;
}
else
{
# ha feltétel1 igaz volt, a többi feltétel nem
utasítások;
}
utasítás if (feltétel);
utasítás unless (feltétel);
print "Túl nagy szám" if ($szam>9999);
print "Hiba!!!" unless ($hiba==0);
Ciklusszervezési lehetőségek
Gyakran lehet szükségünk arra is, hogy a programunk egy részét többször végrehajtsuk, például amíg egy feltétel igaz, vagy amíg egy feltétel hamis. Erre a Perl nyelv számos lehetőséget biztosít. Képzeljük el, hogy mi tennénk, ha ki kellene írnunk az összes négyjegyű egész számot! Biztosan nem leírnánk mindet A példákban ennek megoldási lehetőségeit fogom bemutatni.Az egyik legegyszerűbb ciklusszervezési lehetőség a while ciklus. A szintaktikája a következő:
while (feltétel)
{
utasítások;
}
$szam=1000;
while ($szam<10000)
{
print $szam++."\n";
}
$szam=1000;
until ($szam>9999)
{
print $szam++."\n";
}
$szam=1000; print $szam++."\n" while ($szam<10000);
$szam=1000; print $szam++."\n" until ($szam>9999);
$szam=1000;
do {
print $szam++."\n";
} while ($szam<10000);
$szam=1000;
do {
print $szam++."\n";
} until ($szam>9999);
for (utasítás1; feltétel; utasítás2)
{
ciklusmag;
}
for ($szam=1000; $szam<10000; $szam++)
{
print $szam."\n";
}
for (;;) {print "Végtelen ciklus!";}
for ($x=0, $y=1; $x<10; $x++, $y+=2) { print $x.' '.$y."\n";}
while ($line=EgyUjSor)
{
if ($line eq 'kilép') { last }
if ($line eq 'üdv') { print "Hello!\n"; next }
if ($line eq 'hello') { $line='üdv'; redo }
print "Nem értem.\n";
}
Ennek a résznek a végére a rendkívül jól használható foreach utasítás maradt. Ez veszi egy tömb összes elemét és mindegyikhez végrehajtja a hozzá tartozó utasításblokkot. Egy példa a használatára:
@tomb=('első','második','satöbbi','utolsó');
$i=1;
foreach (@tomb)
{
print "A $i. elem: $_\n"; $i++;
}
Szubrutinok (Eljárások és függvények)
Mint minden más fejlett nyelv, a Perl is biztosít lehetőséget a gyakran használt programrészek állandó a kódban ismételgetése helyett metódusok használatára, azonban nem tesz különbséget az eljárások, illetve függvények között (eljárás, amit meghívunk, de nem tér vissza értékkel - ilyen a print -, függvény, amit meghívunk, és visszatér egy értékkel - ilyen a szinusz-t megvalósító sin). A deklarációja mindkét metódustípusnak megegyezik, s az, hogy egy metódust mire használunk, csakis tőlünk függ. Egy példán keresztül megpróbálom bemutatni, hogy mit nyerünk akkor, ha metódusokat használunk:
$szam=1;
print "A szám értéke: $szam. Eggyel kisebb: ".($szam-1)."\n";
$szam=5;
print "A szám értéke: $szam. Eggyel kisebb: ".($szam-1)."\n";
$szam=8;
print "A szám értéke: $szam. Eggyel kisebb: ".($szam-1)."\n";
$szam=9;
print "A szám értéke: $szam. Eggyel kisebb: ".($szam-1)."\n";
$szam=1; kiir();
$szam=5; kiir();
$szam=8; kiir();
$szam=9; kiir();
sub kiir
{
print "A szám értéke: $szam. Eggyel kisebb: ".($szam-1)."\n";
}
De nézzük végig, hogy mit is csinál a fenti program, mit is jelentenek az egyes sorai. A
kiir()
utasítások hatására a program átugrik a 'sub' részhez, végrehajtja az ott leírtakat, majd annak végén visszaugrik oda, ahol volt. A 'sub' szó azt jelenti, hogy egy eljárást (szubrutint) szeretnénk most leírni, majd kapcsos zárójelek között a tartalma jön.Mint látható volt, az eljárásunk a 'szam' értékét használja. Felmerülhet bennünk a kérdés, hogy nem lehetne-e még egyszerűbben megoldani a problémánkat? Természetesen lehet, paraméterátadással. Ilyen paraméterátadást használunk akkor is, mikor leírjuk, hogy
sin(30)
. Ekkor felsoroljuk a metódus neve után, hogy milyen változókat, konstansokat szeretnénk átadni a számára, majd ezeket az eljáráson belül használni fogjuk tudni. Nézzük, ezt hogyan lehet megoldani:
kiir(1);
kiir(5);
kiir(8);
kiir(9);
sub kiir
{
($szam)=@_;
print "A szám értéke: $szam. Eggyel kisebb: ".($szam-1)."\n";
}
Bővítsük tovább tudásunkat a függvényekkel, azaz az olyan metódusokkal, ahol egy értéket kapunk vissza. Íme egy példa ennek megvalósítására:
print kob(4);
print kob(3);
sub kob
{
return $_[0]**3;
}
sub kob
{
$_[0]**3;
}
@tomb=paratlanszamok(5,9);
sub paratlanszamok
{
($szam1,$szam2)=@_;
for($szam1++ if ($szam1%2==0); $szam1<$szam2; $szam1+=2)
{
push(@ret,$szam1);
}
return @ret;
}
A másik megoldás a cím szerint átadott paraméterek használatával történik. Perlben is kétféleképpen használhatjuk a paramétereket, az egyik, mikor létrehozunk egy új változót és azt használjuk, módosítjuk (ahogy a köbös példa kivételével eddig tettük). Ez az érték szerinti átadás megfelelője. A másik módszer, mikor az '_' tömb elemeire közvetlenül hivatkozunk, ha megváltoztatjuk ezek értékét, akkor a "külső" változó értéke is módosul (tehát Perlben valójában csak cím szerinti átadás történik). Lássunk erre is egy példát:
@tomb=(4,7,2,8,2,4,7,9);
rendez(@tomb);
print "A tömb elemei: ".join(',',@tomb)."\n";
sub rendez
{
@_= sort({lc($a) cmp lc($b)}, @_);
}
Apache log vizsgálata
A mai anyag elég tartalmasra sikerült, de még mindig nincs vége. Egy rövid programot következik, mely egy Apache naplófájl egyszerű elemzésére képes (de könnyen kiegészíthető kívánalmaink szerint).
#!/usr/bin/perl
open(LOG, "access.log");
while(<LOG>) { ++$linenum;
undef @elements;
while (m{
( [^\[\" ][^\" ]* )
|
\"
(
(?: [^\"] | "" )*
)
\"
|
(\[
[^\]]+
\])
}gx) {
if (defined $1) { $elem=$1; }
elsif (defined $2) { $elem=$2; $elem=~s/""/\"/g; }
else { $elem=$3; }
push @elements,$elem;
}
@values=('ip','rname','ruser','datetime','query','status','size','referrer','agent');
for($i=0; $i<@values; $i++) {
$l{$values[$i]}=$elements[$i];
}
($refurl)= $l{referrer}=~/^https?:\/\/([^\/]+)\/?.*$/;
$referrers{$refurl}++;
$date = substr($l{datetime},1,11);
$count++;
$ips{$l{ip}}++;
$lastdate = $date if $lastdate eq '';
if ($lastdate ne $date) {
printinfo();
}
}
printinfo();
sub printinfo {
print "$ldate: $count\n";
print " külön IP-k: ".(keys(%ips)+0)."\n";
foreach (sort {$referrers{$b}<=>$referrers{$a}} keys %referrers) {
print "$_ ($referrers{$_}, $referrers_ip{$_}) ";
}
print "\n";
$count=0;
$lastdate=$thistime;
undef %ips; undef %referrers;
print "--[ $linenum ]-------------------------------------\n";
}
Nos, már ennyi jutott a Perlből, legközelebb folytatjuk az ismerkedést.
javíás
helyesen $_[0]**3;
igy van asszem
Jól tudod
-boogie-
GONDD...
Ezzel itt gondd van Bandikám!
Igaz
-boogie-
Hiba
Hiba:
<Nincs cím>
for és push
$szam1
), addig, amíg el nem jutunk a végértékig ($szam2
).A
for
ciklus három részből áll. Az első részben inicializáció, a második részben feltételvizsgálat, a harmadik részben pedig egy művelet van. Az inicializáció afor
ciklus elején fut le, a ciklus addig fut, amíg a feltétel igaz, s minden ciklus végén lefut a művelet.A
$szam1++ if ($szam1%2)==0
, vagyis az inicializáció nem csinál mást, mint növeli eggyel a kezdőértéket, ha az páros (kettővel történő maradékos osztás végeredménye 0). Ez azt biztosítja, hogy az első szám, amit el fogunk tárolni, az páratlan legyen.A
push
két paramétert vár, egy tömböt és egy értéket, a tömb végére beszúrja a kapott értéket. Végül ennek a tömbnek az értékével tér vissza a függvényünk.-boogie-