Tartalmi kivonat
Perl és CGI programozás A Perl programnyelv alkotója, Larry Wall eredetileg rendszeradminisztrációs feladatokra szánta, de mára rengeteget fejlődött, szinte mindenre használható. Egy interpretált nyelvről van szó, mely annyit takar, hogy a "gép nyelvére" való fordítás futtatási időben történik. Fordító szinte az összes platformra létezik, legyen az Unix, Windows, Mac, stb. Talán épp ezért olyan elterjedt, hiszen az interaktív, CGI-ket használó weboldalak nagy részét Perl-el készítették. A Perl programnyelv alkotója, Larry Wall eredetileg rendszeradminisztrációs feladatokra szánta, de mára rengeteget fejlődött, szinte mindenre használható. Egy interpretált nyelvről van szó, mely annyit takar, hogy a "gép nyelvére" való fordítás futtatási időben történik. Fordító szinte az összes platformra létezik, legyen az Unix, Windows, Mac, stb. További előny, hogy ellentétben más programnyelvekkel, a teljes
Perl ingyenes, szabadon letölthető a Perl weboldalról: http://www.perlcom/ A nyelv tanulása egyszerű, és gyorsan megy, különösen, ha már ismersz más nyelve(ke)t is. Talán épp ezért olyan elterjedt, hiszen az interaktív, CGI-ket használó weboldalak nagy részét Perl-el készítették. 1. Perl alapok Helló világ! Talán kezdjük is el, nézzünk meg egy minimális programot, amely a "Helló világ!" szöveget írja ki: print "Helló világ! "; #kiírja, hogy Helló világ! A programot futtatva kiíródik az üzenet. A fenti egy sorról már sok mindenre lehet következtetni Mint látható, a kiírás a print utasítással történik. A C-hez hasonlóan a szövegek nem tartalmaznak sortörés karaktert, ezt nekünk kell kitenni, a szolgálja ezt a szerepet. A sort mindig pontosvessző zárja, ez kötelező Kommenteket #-al írhatunk, a # után következő karaktereket a Perl nem értelmezi. Adattípusok A Perl nyelv 5 adattípust ismer, ezek
közül most néggyel fogunk részletesebben foglalkozni. Mindegyik típus külön azonosító jellel rendelkezik. Ezek a következők: Skalár változó ($) - egyszerű sztring, vagy szám Tömb (@) - tömb (több skalár), számmal indexelve (0-tól indul) Hash (%) - tömb, sztringgel indexelve (kulcs-érték párok) Szubrutin (&) - egy szubrutint (alprogramot) hív meg Globális (*) - bármely ugyanolyan nevű adattípusra hivatkozik A változó nevének első karaktere határozza meg annak típusát. Nézzünk néhány példát: $valtozo #egyszer?, skalár változó $tomb[3] #tomb nev? tömb 4. elemére való hivatkozás (a számozás 0-tól indul) @tomb #tomb nev? tömb egésze $sash{kulcs} #sash nev? sash kulcs index? értéke %sash #sash nev? sash egésze A változók egyébként típusoktól függően külön tárolódnak, ergo két különböző típusú változónak lehet ugyanaz a neve. Például az $valami[0] a @valami első eleme, nem az $valami skalár Skalár
változók A skalár adattípus egyszerű szám vagy sztring (karaktersorozat) tárolására alkalmas, nevét egy $-nak kell megelőznie. Értékadáshoz az értékadó operátort használjuk, egy egyenlőségjelet (=) - az operátorokról később lesz szó. Nézzünk meg egy egyszerű példát: $nev = "Kiss István"; $kor = 35; Itt a $nev változó a Kiss István értéket kapta, a $kor pedig a 35-öt. Mint látható, sztring esetén idézőjelet kell használni, ez számnál elhagyható. A sorvégi pontosvessző kötelező! Egy változóhoz hozzárendelhetjük egy másik változó értékét is: $evszam = 1990; $szam = $evszam; #$szam erteke 1990 lesz Szövegek összefűzése az alábbi módon történik: $valami = "x"."y"; #$valami erteke xy lesz Skalár változók felhasználhatók sztringen belül is, ekkor a változó beszúrását a Perl végrehajtja: $nev = "Minta Péter"; $udvozlet = "Helló, én $nev vagyok."; Ekkor a
$udvozlet tartalma "Helló, én Minta Péter vagyok." lesz (idézőjelek nélkül természetesen) De nézzünk meg egy érdekesebb példát! $nev = "Minta Péter"; $udvozlet = Helló, én $nev vagyok. ; print $udvozlet; A képernyőre a következő szöveg kerül kiírásra: Helló, én $nev vagyok. De miért? Az ok a használt idézőjelekben keresendő. Ugyanis mint látható, itt nem kettős (") idézőjelet használtam, csak szimplát A Perl a sztringen belüli beszúrást csak " használata esetén végzi el. Itt ejtenék szót az általános levédő karakterről is, mely használatával " idézőjelek között is lehetséges a változóbeszúrás elkerülése. Például: $nev = "Minta Péter"; $szoveg = "Az $nev változó tartalma: $nev"; print $szoveg; A fenti kódrész ezt írja ki: Az $nev változó tartalma: Minta Péter Ha a levédő karaktert szeretnénk kiíratni, akkor önmagát kell önmagával levédeni, azaz: \
Tömbök Egy tömbben több skalár tárolására van lehetőségünk. Ez az egyik legalapvetőbb adatstruktúra, minden nyelvben megtalálható. A Perl-ben a tömbök kezelése dinamikus, mely azt jelenti, hogy ellentétben más nyelvekkel automatikusan kapják a méretüket, és ez automatikusan nő illetve csökken. A tömb nevét @ előzi meg. Létrehozáskor zárójelek között kell felsorolni az elemeket, az alábbi módon: @allatok = ("kutya","cica","madár","csiga","hal"); Egy allatok nevű tömböt hoztunk létre, mely 5 elemet tartalmaz. Az elemeknél lehetőség van skalárok, vagy tömbök beszúrására is, nézzünk erre is egy példát! $allat = "viziló"; @madarak = ("papagáj","veréb","kakukk","strucc"); @allatok = ("kecske",$allat,"őz",@madarak,"egér"); A fenti példában először egy $allat változót hoztunk létre, mely a
viziló értéket kapta. Aztán egy @madarak tömb következett, melyben madarakat soroltam fel. Végül egy @allatok tömböt készítettünk, mely megkapta az $allat, és a @madarak tartalmát is, és még néhány elemet. Ezzel a @allatok tartalma a következő lett:: kecske, viziló, őz, papagáj, veréb, kakukk, strucc, egér Most nézzük meg, hogyan hivatkozhatunk a tömb egyes elemeire! @evszakok = ("tavasz","nyár","ősz","tél"); print "A @evszakok tömb első eleme: $evszakok[0]."; A program kiírja: A @evszakok tömb első eleme: tavasz. Mint látod, az elemek számozása nullától indul, erre oda kell figyelni! Ezzel a formával tömbök létrehozása, illetve már létező tömbök elemeinek megváltoztatása is lehetséges: @evszakok = ("tavasz","nyár","nyár","tél"); print "A @evszakok tömb harmadik eleme: $evszakok[2]. "; $evszakok[2] = "ősz"; print
"A @evszakok tömb harmadik eleme: $evszakok[2]."; A fenti kódsor először a @evszakok tömb 3. elemének a nyár-t nevezi meg, majd a megváltoztatás után az őszt Ha a tömböt egy skalárhoz rendeljük hozzá, akkor az a tömb hosszát (az elemek számát) kapja értékül Például: @szinek = ("piros","kék","zöld","sárga"); $hossz = @szinek; print $hossz; A program kiír egy 4-est, mivel létrehoztunk egy @szinek tömböt, mely négy sztringet tartalmazott, majd a $hossz értékének a @szinek -et adtuk meg, így az $hossz a tömb elemeinek számát, a 4-est kapta értékül, mely a következő sorban kiírásra került. Lehetőségünk nyílik un tömbszeletek kezelésére is, ezzel a tömbnek egy részére hivatkozhatunk, az alábbi módon: @szinek = ("piros","kék","zöld","sárga"); $szelet = @szinek[2,3]; print $szelet; Ekkor kiírásra kerül, hogy zöldsárga, mivel ez volt a
tömb 3. és 4 eleme Ezzel könnyedén felcserélhetünk két tömbelemet, nem kell külön skalár változót bevonni, amiben ideiglenesen tárolásra kerülne az egyik elem, az egész egy sorral megoldható: @szinek[1,2] = @szinek[2,1]; Ezzel a zöldet előrehoztuk a kék helyére, a kéket pedig a zöld helyére. Most pedig essen szó s push és a pop operátorokról. Segítségükkel a tömb végét alakíthatjuk anélkül, hogy tudnánk a tömb hosszát A push használatával a tömb végéhez lehet csatolni további eleme(ke)t, a pop épp ezzel ellentétes, a tömb utolsó elemét lehet vele eltávolítani. Nézzünk erre egy példát! @szinek = ("piros","kék","zöld","sárga"); $szin = "lila"; push(@szinek,$szin,"narancs$szinek[3]"); $utolsoszin = pop(@szinek); Na, akkor mit is csináltunk? Először létrehoztunk egy négyelemű tömböt (@szinek), majd egy skalárt ($szin), melynek a lila értéket adtuk. Aztán a
@szinekhez hozzácsatoltuk a $szin-t, és még egy sztringet, mely a narancs, és a @szinek tömb 4. elemének az összetételéből következett, azaz a narancsból, és a sárgából Ezzel csak azt szerettem volna bemutatni, hogy itt is használható egyszerű skalár éppúgy, mint sztring, vagy akár ezek kombinációja. Tehát ekkor a tömbünk elemei a következők: piros, kék, zöld, sárga, lila, narancssárga Végül a tömb utolsó elemét levágjuk, és egy változóban tároljuk. Van két hasonló operátor, a shift és az unshift, melyek ugyan ezt a célt szolgálják, csak nem a tömb végét, hanem az elejét van lehetőségünk velük alakítani. Nézzünk ezekhez is egy példát! @szinek = ("zöld","sárga"); $szin = "piros"; unshift(@szinek,$szin,"kék"); shift(@szamok); Itt létrehoztunk egy kételemű tömböt, majd egy skalárt. Az unshift operátorral az elejéhez tettünk további két elemet, a $szin-t, és egy új
sztringet. Ekkor a @szinek elemei: piros, kék, zöld, sárga Végül a shift-el eltávolítottuk az első elemet, így a tömb elemei: kék, zöld, sárga. A tömbökkel kapcsolatban szót ejtek még két operátorról a reverse-ről és a sort-ról. A reverse használatával a tömb elemeinek sorrendjét felcserélhetjük. Ez az operátor nem írja felül az eredeti tömböt, ezért egy másikat kell létrehozni, amely ugyan azokat az elemeket fogja tartalmazni, mint az eredeti, csak fordított sorrendben (hivatkozhatunk az eredeti tömbre is, ekkor felülírja). Lássunk erre is egy példát! @szamok = (1,2,3,4,5); @visszafele = reverse(@szamok); Ekkor a @visszafele elemei a következők lesznek: 5, 4, 3, 2, 1. Itt említeném meg a használatát, ha számokból álló tömböt hozunk létre, ezzel lerövidíthetjük a létrehozást: @szamok = (1.5); Ezzel ugyan azt értem el, mint a fenti formával, azaz a tömb elemei a következők lettek: 1, 2, 3, 4, 5. Ez használható
betűknél is (a-z). Fentebb említettem még a sort operátort Ezzel egy tömb tartalmát rendezhetjük: @szinek = ("piros","kék","zöld","sárga"); @szinek = sort(@szinek); Mint látható, ez sem írja felül az eredeti tömböt, így ha felül szeretnénk írni, akkor önmagára kell hivatkoznunk. A sort hatására a @szinek tartalma a következő lett: kék, piros, sárga, zöld @szamok = (12,5,437,9,28); @szamok = sort(@szamok); Ekkor a @szamok tartalma: 12, 28, 437, 5, 9. De vajon miért? Mert a rendezés karakterenként történik, a számokat ugyanúgy rendezi, mintha szöveg lenne, azaz először az első számjegy alapján állítja őket sorrendbe, majd ha van egyező, azoknál kezdi el vizsgálni a második számjegyet. Hash A hash (más néven asszociatív tömb) abban hasonlít a fent tárgyalt tömbhöz, hogy ebben is skalárok tárolására van lehetőség, azonban itt az indexelés nem számokkal, hanem sztringekkel
történik, azaz itt egy tömbelemre nem a száma, hanem a neve szerint hivatkozunk (tehát létrehozáskor ezt is meg kell adni). Nézzünk egy példát hash létrehozására! %sokminden = ("szín","piros","hónap","június","virág","rózsa"); Ez így elég nehezen nyomon követhető, ezért van egy másik forma is, a magyarázat előtt nézzük meg azt: %sokminden = ( "szin" => "piros", "honap" => "június", "virag" => "rózsa" ); Tehát, mint látható, létrehoztunk egy %sokminden nevű asszociatív tömböt, melynek három elemet adtunk: szín, hónap, virág, és ezeknek adtunk értéket is, piros, június, rózsa. Az egyes elemekre való hivatkozás itt is egyszerű skalárként történik, a kívánt elem nevének kapcsos zárójelbe való írásával: print $sokminden{"honap"}; A program kiírja, hogy június, mivel fent ezt adtuk
meg. Természetesen ez a forma használható új tömb létrehozására, már meglévő elemek módosítására, és új elem behozatalára is: %sokminden = ( "szin" => "piros", "honap" => "június" )é $allatok{"madar"} = "papagáj"; $allatok{"kutya"} = "huski"; $nev = "madar"; $sokminden{"kutya"} = $allatok{"kutya"}; $sokminden{$nev} = $allatok{"madar"}; $sokminden{"szin"} = "sárga"; Na, ez már bonyolultabb. Tehát, hogy is van ez? Először létrehoztunk egy %sokminden nevű tömböt, mely két elemet kapott, a szin-t és a honap-ot. Aztán egy %allatok tömb került létrehozásra, azonban ez a hivatkozási formával, először kapott egy madar, majd egy kutya elemet. Aztán létrehoztunk egy egyszerű skalárt ($nev) Ez után a %sokminden tömbhöz létrehoztunk egy új elemet, melynek a neve kutya lett, ehhez rendeltük értéknek a
%allatok tömb kutya nevű értékét. Aztán a %sokminden-t újabb elemmel bővítettük, melynek neve az $nev változó tartalma lett, értéke pedig a %allatok tömb madar értéke lett. Végül a %sokminden szin nevű paraméterét változtattuk meg sárgá-ra. Tehát a %sokminden tartalma most: szin=>sárga, honap=>június, kutya=>huski, madar=>papagáj. Az asszociatív tömbök esetén is megemlítek néhány operátort: keys, values, delete. Kezdem is az elején, a keys operátorral, melynek használatával a tömbben lévő kulcsokat kaphatjuk meg. Itt egy példa: %sokminden = ( "virag" => "rózsa" ); "szin" => "piros", "honap" => "június", print keys(%sokminden); Kiírja, hogy viragszinhonap. Ennek később, a ciklusoknál fogja sok hasznát venni, ha egy hash összes elemén végig akarsz menni. Ha a keys operátort hozzárendelésnél használjuk, akkor a kulcsok számát adja vissza:
%sokminden = ( "szin" => "piros", "honap" => "június", "virag" => "rózsa" ); $hossz = keys(%sokminden); print $hossz; #kiírja, hogy 3 A következő operátor, a values hasonló, csak ez nem a kulcsokat, hanem az értékeket adja vissza: %sokminden = ( "virag" => "rózsa", "szin" => "piros", "honap" => "június" ); print values(%sokminden); Kiíródik a következő karaktersorozat: rózsapirosjúnius. A hozzárendelést egy tömbhöz is végezhetjük: %sokminden = ( "virag" => "rózsa", "szin" => "piros", "honap" => "június" ); @ertekek = values(%sokminden); print $ertekek[1]; Mint látható, az értékeket egy tömbben tároltuk, majd kiírattuk a tömb második elemét, azaz a június-t. Végül essék szó a delete operátorról, mely meglepő módon :) törlésre
használható: %sokminden = ( "virag" => "rózsa", "szin" => "piros", "honap" => "június" ); delete $sokminden{honap}; %sokminden tartalma: virag=>rózsa, szin=>piros lesz. Szubrutinok Az alprogramok jele a &, tehát meghívásnál ezt kell alkalmazni. Deklarációnál (tehát ott, ahol leírjuk, hogy mit is csináljon a szubrutin) a sub-ot kell használni. Nézzünk egy egyszerű alprogramot! &alma; exit; sub alma { print "Ezt az üzenetet az alma nev? szubrutin írta ki."; } Mint látható, először meghívtuk az alprogramot. Aztán egy exit utasítás következett, ez a programból való kilépésre, vagy a program megszakítására szolgál. Itt nem lett volna kötelező használni, csak azért használtam, hogy lásd, általában a szubrutinok deklarálása a program végén szokás. Persze ez nem kötelező, csak így áttekinthetőbb. Aztán következett maga a deklaráció,
melyet egy sub utasítás kiadásával lehet elkezdeni, ezt az alprogram neve (ahogy hivatkoztunk rá) követi, majd egy utasításblokk, Tehát egy { majd az utasítások felsorolása, végül egy }. Ilyen utasításblokkokat még sok helyen fogunk használni A blokkon belül nem szükséges a sorok elé szóközöket vagy tabulátorokat tenni, ez csak az áttekinthetőséget javítja (hosszabb programok esetén érdemes használni). Nézzük meg a fenti példát egy kicsit más sorrendben! sub alma { print "Ezt az üzenetet az alma nev? szubrutin írta ki."; } alma; exit; Itt a hivatkozás a deklaráció után történt, és nem volt szükség az & jelre, mivel itt a hivatkozáskor az alma már értelmezve volt. Azonban az előző esetben a Perl-nek jelezni kellett, hogy egy szubrutinról van szó Alprogramoknál használatos a return utasítás, mellyel visszatérési értéket adhatunk meg: $udvozlet = &alma; print "$udvozlet "; exit; sub alma { $szoveg
= "Helló!"; return $szoveg; } Itt az alma szubrutinban egy $szoveg változóba tároltunk egy sztringet, a hivatkozáskor pedig az egész alprogramot egy változóhoz rendeltük hozzá, majd kiírattuk. Természetesen lehetőség van adatok átvitelére is, ezeket a hivatkozáskor kell megadni, majd az alprogramban a @ tömb elemeiként kapjuk meg őket: &udvozlet("Helló","Minta Péter"); exit; sub udvozlet { print "$ [0], az én nevem $ [1]. "; } A hivatkozásnál két paramétert adtunk meg, két sztringet. Az alprogramban pedig a két adat felhasználásával egy üdvözlő szöveget írtunk ki, feltételezve, hogy az első adat egy köszönési forma, a második pedig egy név. @elemek = ("könyv","telefon","internet","magazin"); &utolso(@elemek); exit; sub utolso { $utolso = pop; print "Az utolsó elem: $utolso "; } A program kiírja, hogy magazin. De miért? Először
létrehoztam egy tömböt (@elemek), majd hivatkoztam az alprogramra, a @elemek tömb tagjait megadva paraméterként. A szubrutinban pedig egy változóban tároltam az átadott elemeket tartalmazó tömb utolsó elemét. Ami érdekes, hogy a @ nevet meg sem említettem, a pop operátort paraméter nélkül használtam. Ez azért van, mert mindig az nevű adattípus az alapértelmezett, tehát ha valahol nem adok meg semmit, akkor ezt fogja használni. Mára ennyit, a következő részben az operátorokról, programszerkezetekről lesz szó. A későbbiekben a példaprogramokat megtalálhatja mellékelve, egyelőre nem láttam szükségesnek, mivel ezek még igen egyszerű programok. Folytatás a következő hónapban 2. Operátorok és elemi műveletek Az előző számban a Perl adattípusairól volt szó, most az operátorokról fogunk beszélni, mert ezt fontosnak tartom, mielőtt rátérünk a programszerkezetekre. Az egyik legalapvetőbb operátorral, az értékadó
operátorral a múltkori számban már megismerkedtünk Persze nem ez az egyetlen, ami ezt a célt szolgálja, talán kezdjük is egy kis összefoglalóval, hogy milyen más módjai vannak még az értékadásnak! $szam $szam $szam $szam $szam $szam = 10; += 4; #$szam erteke -= 5; #$szam erteke *= 2; #$szam erteke /= 3; #$szam erteke .= " db alma"; 14 lesz 9 lesz 18 lesz 6 lesz Amint látható, egy $szam változót hoztunk létre, majd különböző műveleteket hajtottunk vele végre. Most nézzük meg a fenti példát úgy, hogy értékadáshoz csak a = operátort használom, a számolásokat pedig aritmetikai operátorokkal végzem el: $szam $szam $szam $szam $szam $szam = = = = = = 10; $szam $szam $szam $szam $szam + * / . 4; #$szam erteke 5; #$szam erteke 2; #$szam erteke 3; #$szam erteke " db alma"; 14 lesz 9 lesz 18 lesz 6 lesz Ezzel ugyanazt érem el, mint az előző példával. Természetesen az aritmetikai operátorok használhatók
változókkal és számokkal is A operátor változók összefűzésére szolgál. Nézzünk meg még egy példát! $a = 10; $b = 2; $c = $a * $b; #$c értéke 20 $d = 2 * 5; #$d értéke 10 $d += $b * $c; #$d értéke növekszik (220) -al, tehát 50 lesz print $d - $b; #kiírja, hogy 48 Mint látható, létrehoztam két változót, majd egy harmadikba a kettőt összeszoroztam. Aztán egy negyedik változóba két számot szoroztam össze, utána pedig növeltem az értékét az első két szám szorzatával, végül levontam belőle $b értékét, és az eredményt kiírtam. Utóbbi két műveletet egy sorban is el lehet végezni. Most pedig nézzünk meg még két operátort, a maradékképzőt és a négyzetre emelőt: print print print print 10 % 3; #kiírja, hogy 1 8 % 3; #kiírja, hogy 2 2*2; #kiírja, hogy 4 5*2; #kiírja, hogy 25 Mint látja, a % operátor maradékot képez, hatványozásra pedig a * szolgál. Most nézzünk meg egy érdekesebb példát! print -4 * 3;
#kiírja, hogy -16 Ez vajon hogy lehet, hiszen -4 a négyzeten, az 16? - vetődik fel a kérdés. A válasz egyszerű, a Perl a hatványozást magasabb rendű műveletként kezeli, ezért előbb elvégzi, és utána teszi ki az előjelet. $x = -4; print $x*2; #kiírja, hogy 16 Itt először eltároltam a negatív számot egy változóba, majd a változót hatványoztam, a kimenetre így már 16 kerül. A Perl külön operátorokkal rendelkezik az inkrementáláshoz és a dekrementáláshoz, ez a ++ és a --. Mindkettő tehető a változó elé és mögé is, a különbség az, hogy ha a változó előtt van, akkor az értéket előbb növeli/csökkenti, és a visszatérési érték már az új érték lesz, viszont ha a változó mögött van, akkor a visszatérési érték az eredeti érték lesz, és csak ez után fogja növelni az értéket. Nézzünk egy példát! $a = 1; $b = ++$a; print "$a értéke: $a $b értéke: $b "; A kimenetre ez kerül: $a értéke: 2 $b
értéke: 2 Most nézzük meg ugyanezt, ha a ++ a változó mögött van: $a = 1; $b = $a++; print "$a értéke: $a $b értéke: $b "; A kimenetre ez kerül: $a értéke: 2 $b értéke: 1 A különbség egyértelmű, az első példában az $a értékét először növelte, ezért a visszatérési érték már 2, a másodikban viszont csak utána, ezért a visszatérési érték még 1 volt. Ugyan ez a helyzet a -- -nál is: $a = 2; $b = --$a; print "$a értéke: $a $b értéke: $b ------------ "; $a = 2; $b = $a--; print "$a értéke: $a $b értéke: $b "; Ahol a kimenet: $a értéke: 1 $b értéke: 1 -----------$a értéke: 1 $b értéke: 2 Essen szó még egy operátorról, az ismétlő operátorról (x), mely egy sztringet sokszoroz meg, az alábbi módon: print "-" x 10; #kiírja, hogy ---------Az ismétlés operátorral tömböket is hozhatunk létre, ekkor zárójelekkel kell ezt jelölni: @tomb = ("-") x 5;< print
$tomb[2]; Itt egy 5 elemű tömb került létrehozásra, melynek minden eleme egy - karakter, majd kiírtuk a tömb 3. elemét Most pedig essen szó az összehasonlító operátorokról, melyek a következők: > (nagyobb, mint) < (kisebb, mint) >= (nagyobb vagy egyenlő) <= (kisebb, vagy egyenlő) != (nem egyenlő) <=> (összehasonlítás) Amikor két adatot összehasonlítunk, és az összehasonlítás igaz, akkor a Perl egy 1-est fog visszaadni, ha nem igaz, akkor nullsztringet. Kivétel az összehasonlítás, ahol ha nagyobb, akkor 1-et ad vissza, ha kisebb, akkor -1-et, ha egyenlő, akkor 0-át. Tekintsünk meg néhány példát! $igaz = (4 != 3); $hamis = (10 < 10); print $igaz; #kimenet: 1 print $hamis; #nincs kimenet print (2 > 1); #kimenet: 1 print (4 <= 4); #kimenet: 1 print (1 >= 3); #nincs kimenet print (4 <=> 3); #kimenet: 1 print (4 <=> 4); #kimenet: 0 print (4 <=> 5); #kimenet: -1 A fenti operátoroknak létezik egy
másik változata is, melyet sztringek összehasonlításánál használunk: eq (egyenlő) ne (nem egyenlő) gt (nagyobb, mint) lt (kisebb, mint) ge (nagyobb, vagy egyenlő) le (kisebb vagy egyenlő) cmp (összehasonlítás) Most pedig beszéljünk a logikai operátorokról (and, or, not - &&, ||, !), aki már jártasabb egy kicsit ezen a téren, az már biztosan találkozott velük. Több kifejezést lehet segítségükkel vizsgálni, az and akkor tér vissza igaz értékkel, ha mindkét feltétel igaz, az or a bal oldalival ha igaz, ha nem, akkor a jobbal, a not pedig igazzal tér vissza, ha mindkét kifejezés hamis: print (5 > 1) && (10 > 9); #kimenet: 1 A kimenet 1, vagyis true, mert mindkét állítás igaz. Viszont az alábbi példa már nem fog kiírni semmit (false): print (5 < 1) && (10 > 9); #kimenet: 1 Mint látható, az && operátornál elég, ha csak az egyik kifejezés hamis, a visszatérési érték már false lesz. Most
pedig térjünk rá a vezérlési szerkezetekre! A vezérlési szerkezetek általában egy feltételből, és egy utasításblokkból állnak. A feltétel feladata, hogy az utasításblokkot a feltétel logikai értékének megfelelően hajtsa végre. Mi is az az utasításblokk? Egy { és } -el elhatárolt programrész, melyekben tetszőleges számú utasítás található. Az egyik leggyakrabban használt vezérlési szerkezet az if/elseif/else Nézzünk egy egyszerű példát az if használatára! $a = 1; $b = 3; if ($a < $b) { print Az $a változó értéke nagyobb, mint az $b-é.; } Két skalár került létrehozásra ($a, $b), majd egy elágazás következett, melyben megvizsgáltuk, hogy az $a értéke nagyobb-e, mint az $b értéke, és ha ez igaz volt, akkor kiírattunk egy sztriget. Nézzük meg a fenti példát az else utasítással kibővítve Az else után következő utasításblokk akkor hajtódik végre, ha az if után következő feltétel nem igaz. $a = 1; $b =
3; if ($a == $b) { print Az $a változó értéke egyenlô az $b-ével.; } else { print A feltétel nem igaz!!; } Itt a fenti két változóról azt vizsgáltuk meg, hogy egyenlők-e egymással (az = operátor értékadásra van, egyenlőségvizsgálatnál == -et használunk). Mivel nem egyenlők, "A feltétel nem igaz" sztring került kiírásra Az if-nek az else-en kívül van még egy párja, az elsif mely az else-hez hasonlóan akkor hajtódik végre, ha az if nem igaz, viszont további feltételt szab meg. Az elsif után következhet mégegy else is: $a = 1; $b = 3; if ($a == $b) { print Az $a változó értéke egyenlô az $b-ével.; } elseif ($a > $b) { print Az $a változó értéke nagyobb, mint az $b-é.; } else { print Az $b változó értéke nagyobb, mint az $a-é.; } Persze if/else/elseif utasításokat egymásba is ágyazhatunk: $a = 10; $b = $a + 1; if ($a != $b) { if ($a > $b) { print $a nagyobb, mint $b; } else { print $b nagyobb, mint $a; } }
else { if ($a < 10) { print $b egyenlo $a, es 10-nel kisebb; } elsif ($a > 10) { print $b egyenlo $a, es 10-nel nagyobb; } else { print $b egyenlo $a, azaz 10; } } Mit is csináltunk? Ugye, hogy milyen olvashatatlan? Nézzük meg a fenti programrészt kicsit máshogy! $a = 10; $b = $a + 1; if ($a != $b) { if ($a > $b) { print $a nagyobb, mint $b; } else { print $b nagyobb, mint $a; } } else { if ($a < 10) { print $b egyenlo $a, es 10-nel kisebb; } elsif ($a > 10) { print $b egyenlo $a, es 10-nel nagyobb; } else { print $b egyenlo $a, azaz 10; } } Így már mindjárt olvashatóbb. A program futásánál ugyan nincs szerepe a tabulátoroknak, mégis ajánlott a használatuk, mert bonyolultabb programszerkezeteknél kegyetlenül olvashatatlanokká válhatnak programjaink. A második részt nézve bizonyára Ön is rájött, hogy mit csinál a kód. Két változót hoztunk létre, az egyiknek eggyel nagyobb értéket adva, mint a másiknak Aztán megvizsgáltuk, hogy nem
egyenlők-e egymással (a != összehasonlításnál az == ellentéte). Ha egyenlők, akkor a program megnézi, hogy tíznél nagyobb, kisebb, vagy esetleg pont 10 a változók értéke (mivel ha ide jutunk, akkor az már biztos, hogy egyenlők a változók értékei, tehát elég csak az egyik értékét megvizsgálni), ha nem egyenlők, akkor azt nézzük meg, hogy melyik a nagyobb. Persze ez a program így elég értelmetlennek tűnik, nem igazán használható a gyakorlatban, de úgy gondolom, hogy tanuláshoz jó. Tekintsünk meg még egy példát, melyben két feltételt kötünk egy utasításblokk végrehajtásához! $a $b $c if = 1; = $a + 1; = $b + 1; ( ($a < $b) and ($b < $c) ) { print "$a < $b < $c"; } elsif ( ($a > $b) and ($b > $c) ) { print "$a > $b > $c"; } Létrehoztunk három skalárt, majd megvizsgáltuk őket, és ha a harmadik volt a legnagyobb, az első pedig a legkisebb ($a < $b és $b < $c), akkor kiírtuk a
három változó értékét, a megfelelő relációjeleket kitéve közéjük. Ha ez az állítás pont fordítva volt igaz, azaz az $a volt a legnagyobb, és az $c a legkisebb, akkor is kiírtuk a számokat, csak a relációjelet fordítva tettük ki. Van egy másik elágazásfajta is, ez az unless, mely nagyon hasonló az if-hez, különbség csak abban van, hogy míg az if használatánál a feltétel utáni utasításblokk akkor került végrehajtásra, ha a feltétel igaz volt, itt épp ellenkezőleg, akkor kerül végrehajtásra, ha a feltétel hamis: $a = 10; $b = 2; unless ($a < $b) { print $a nagyobb, mint $b; } else { print $b nagyobb, mint $a; } A program kiírja, hogy "$a nagyobb, mint $b", mivel az $a < $b feltétel hamisnak bizonyult. Ezzel végére értünk az elágazásoknak, a programszerkezeteken belül a következő téma a ciklusszerkezetek lesznek, de azokról a következő számban olvashat, most inkább nézzünk meg egy-két gyakran használt
függvényt, majd egy kész programot! Az első függvény a rand, mely egy random (véletlenszerű) számot fog visszaadni, 0 és az első paraméter értéke között (a paramétert az előző számban tárgyalt szubrutinok meghívásánál megismert módszer szerint adhatja meg). Ha nem adunk meg paramétert, akkor a szám 0 és 1 között lesz. Nézzünk egy példát! $szam = rand(5); print "Véletlenszerû szám: $szam"; A kimenetre ez kerül: Véletlenszerû szám: 2.17788696289062 Persze ez a szám minden futtatáskor más (nagyon kicsi annak az esélye, hogy ugyanazt az értéket írja ki). Mint látható, egy tört számot ad vissza a függvény (általában), ezért ajánlott az int függvény használata, mely egy számnak veszi az egész részét: $szam = 6.454635435768634; print int($szam); #kiírja, hogy 6 Ezt használva a véletlenszerű számot generáló példánál, már egész számot kapunk: $szam = int(rand(5)); print "Véletlenszerû szám:
$szam"; És a kimenet: Véletlenszerû szám: 4 Most pedig készítsünk el egy programot, amelynek egy fiktív háromszög oldalainak hosszát megadva megmondja, hogy a háromszög derékszögű-e. Ehhez a Pitagorasz-tételt fogjuk alkalmazni, azaz derékszögű háromszögben a2+b2=c2 print "Add meg egy háromszög három oldalának a hosszát egységekben! "; #adatok felvétele (a három oldal: $a, $b, $c) print "Az elsô oldal: "; chomp($a = <STDIN>); print "A második oldal: "; chomp($b = <STDIN>); print "A harmadik oldal: "; chomp($c = <STDIN>); #adatok vizsgálata (a^2 + b^2 = c^2 ?) if ( $a*2 + $b2 == $c2) { #ha igaz print "Ez egy derékszögû háromszög!"; } else { #ha hamis print "Ez sajnos nem derékszögû háromszög!"; } exit; Ennyi lenne a program, az eleje nem szorul magyarázatra, ami érdekesebb, az az adatok felvétele: chomp($a = <STDIN>); Ez a sor az $a skalárba
tárolja azt, amit a felhasználó a billentyűzeten beír, az eltárolás az enter billentyű megnyomása után történik. Mivel ez magával hoz egy sortörést is, a chomp függvény segítségével ezt le kell vágni a sztring végéről, és a következő print utasítás kimenete is a következő sorban jelenik majd meg. Ezekkel a dolgokkal (input és output - bemenetek és kimenetek) később fogunk foglalkozni, csak azért használtam ezt a formát, hogy valamivel érdekesebb legyen a program (futás közben kell megadni az értékeket). Miután a háromszög oldalai beolvasásra kerültek ($a, $b, $c), egy egyszerű elágazást alkalmaztam, amiben megvizsgáltam, hogy az első két oldal (a két befogó) négyzeteinek az összege egyenlő-e az átfogó (harmadiknak megadott oldal) négyzetével. A következő részben a ciklusokról és a mintaillesztésről lesz szó. "Házi feladat": egy a fentihez hasonló program elkészítése, mely két számot kér be, és
kiírja azoknak összegét, különbségét, szorzatát, és hányadosát. A feladat megoldását az olvasóra bízom 3. Ciklusok Az előző számot az elágazásokkal fejeztem be, most folytatom a programszerkezeteket, a ciklusokkal. Mi is az a ciklusszerkezet? Eddig az elágazásokat néztük meg, melyek egy kifejezésből és egy utasításblokkból álltak, és a kifejezéstől függően hajtódtak végre, illetve átugrásra kerültek az utasításblokkban szereplő utasítások. A ciklusok is hasonlóan működnek, azonban itt a kifejezéstől függően az utasításblokk többször is végrehajtódhat. Tekintsük meg az első fajtáját a ciklusoknak, a while utasítást. Arra szolgál, hogy amíg a kifejezés logikai értéke igaz, addig mindig végrehajtja az utasításblokkot, ha a kifejezés hamissá válik, a program futása az utasításblokkot lezáró } után folytatódik. Tekintsen meg egy egyszerű példát! $a = 3; $b = 10; while ($a < $b) { $a++; } Két
skalár került létrehozásra, $a és $b. Ezután egy while ciklus addig ismételgetett egy utasításblokkot, amíg $a értéke nem lett nagyobb $b-nél, az utasításblokkban pedig $a értéke került növelésre. Akkor hogyan is zajlott le a program lefutása? Mivel $a értéke 3, $b pedig 10, ezért az $a < $b feltétel igaznak bizonyult. Ekkor a program lefutatta az utasításblokkot, melyben $a értéke növelésre került eggyel, így az értéke már 4 volt. Azonban ez még mindig kisebb, mint az $b értéke, tehát megint lefutott az utasításblokk És ez így ment mindaddig, amíg $a értéke el nem érte a 10-et, akkor ugyanis még egyszer lefutott az utasításblokk (tehát ha a program végén kiírattuk volna $a értékét, akkor 10-et írt volna ki), és itt lett vége. A while testvére az until, mely ugyanazt csinálja, mint a while, annyi különbséggel, hogy az utasításblokkot addig ismételgeti, amíg a feltétel igazzá válik. Tekintse meg az előző
példát az until használatával! $a = 3; $b = 10; until ($a >= $b) { $a++; } Az eredmény ugyanaz lesz, mint az előző példánál. Azonban itt a relációjel megfordításán kívül egy = jel is bekerült, mivel ha az nem lenne, és szigorúan nagyobbnak kellene lenni a $a-nak a $b-nél, akkor a ciklus után az $a a 11 értéket kapná, mivel ha a két változó már egyenlő lenne egymással, még akkor sem lenne a kifejezés értéke igaz (true). A while és az until párja a do.while és a dountil Ezek a szerkezetek valójában nem léteznek Perl-ben, azonban egy do függvény segítségével előállíthatók, akkor használjuk őket, ha azt szeretnénk, hogy az utasításblokk végrehajtásra kerüljön, mielőtt a kifejezés kiértékelése megtörténne. Tekintsen meg egy példát! $szam = 0; do { $szam++; print "$szam;"; } while ($szam < 10); A kimenet az alábbi lesz: 1;2;3;4;5;6;7;8;9;10; Az $szam skalár a 0 értéket kapta, majd az utasításblokkban
az értéke növelésre, majd kiírásra került mindaddig, amíg az értéke kisebb volt, mint tíz (mivel a kiértékelés a végrehajtás után történik, még a 10-es is kiírásra kerül). A dountil-al a fenti példa az alábbi módon oldható meg: $szam = 0; do { $szam++; print "$szam;"; } until ($szam >= 10); A kimenet ugyan az lesz, mint a fenti példánál. A >= operátorra azért volt szükség, hogy a kiértékelés igazzá váljon, ha az $szam a 10-es értéket kapja, mert ellenkező esetben a 11-es is kiírásra kerülne. Próbáljunk meg feltölteni egy tömböt számokkal, a dowhile segítségével! $szam = 0; do { $szam++; push(@szamok,$szam); } until ($szam >= 10); A @szamok tartalma: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10. A következő ciklusszerkezet az egyik leggyakrabban használt, és egyben a legösszetettebb, a for ciklus. A szintaktikája valahogy így néz ki: for (kezdeti érték; ellenőrzés; érték megváltoztatása) { utasításblokk }
A kezdeti értékben egy változónak egy alapértéket adunk meg, majd az ellenőrzésnél egy kifejezést készítünk, melyben ezt a változót vizsgáljuk, ez után pedig a változó értékét módosíthatjuk. A ciklus futása ezek után úgy fog kinézni, hogy létrehozza a változót a kezdeti értékkel, majd azt megvizsgálja a kifejezésben megadottak szerint, és ha a kifejezés nem igaz, akkor lefut az utasításblokk, és elvégzi a változtatást a változón, majd újra ellenőrzi, és ha így már igaz a kifejezés, akkor továbblép, és folytatja a futást a for ciklus után, ha még mindig nem igaz, akkor újból végrehajtja a változtatásokat, és így tovább. Tekintsen meg egy egyszerű példát! for ($x=0; $x<10; $x++) { print "$x "; } Ez a ciklus kiírja az egész számokat 0-9-ig, egymás alá. Egy másik példa, mely ugyanazt csinálja, mint a fenti, csak csökkenő sorrendben írja ki a számokat: for ($x=9; $x>=0; $x--) { print "$x
"; } A kimenetre egymás alá kerülnek a számok, 9-0-ig. Az $x kezdeti értékként a 9-et kapta, majd a vizsgálatnál az operátor "nagyobb, vagy egyenlő" volt, mivel ha csak "nagyobb" lett volna, akkor a 0 kimaradt volna a számsorból (ebben az esetben -1-et kellett volna írni a 0 helyére, így már 0-ig ment volna a számsor). Most pedig nézzünk meg egy bonyolultabb példát, mégpedig próbáljuk meg elkészíteni a 10-es szorzótáblát! Több részletben mutatom be, így biztosan érthető lesz: for ($x=1; $x<=10; $x++) { if ($x < 10) {$x = " $x";} elsif ($x < 100) {$x = " $x";} print "$x "; } Ez egy egyszerű for ciklus, melyben az $x 0-10-ig terjedő értékeket kapja meg. Egyébként ez szolgál a fejléc kiírására Van benne egy érdekes dolog, mégpedig az egy és a kétjegyű számok szóközökkel való feltöltése, hogy a megjelenítésben már ugyanolyan hosszúak legyenek. Ez egy elágazás,
melynek két esete van, az egyik, ha $x kisebb, mint tíz, ekkor két szóközt told a szám elé, a másik csak akkor teljesül, ha az előző hamis volt (elsif ág), és az $x értéke kisebb, mint 100, tehát kétjegyű szám. A következő részlet: for ($x=1; $x<=10; $x++) { if ($x < 10) {$x = " $x";} print "$x|"; for ($y=1; $y<=10; $y++) { $z = $x*$y; if ($z < 10) {$z = " $z";} elsif ($z < 100) {$z = " $z";} print "$z "; } print " "; } Itt tulajdonképpen két, egymásba ágyazott ciklusról van szó. Mind a kettő 1-10-ig megy Az első, először ha egyjegyű, egy szóközzel tölti fel, kiírja az aktuális értéket, és egy függőleges vonalat (a megjelenítés miatt, ez majd lentebb fog látszani, a kimeneten) tesz utána, majd következik a második ciklus, majd egy sortörés, így fog létrejönni egy sor, az $x értékek kerülnek majd a bal oldali, függőleges oszlopba. A másik for ciklus
pedig az értékeket fogja generálni, egy új változó bevonásával ($z), melyben összeszorozza a két for ciklus változóinak ($x, $y) jelenlegi értékeit, majd ezeken is végrehajtja a fentebb megismert szóközzel való feltöltést, és kiírja azt (utána egy szóközzel, hogy valamennyire tagoltan jelenjenek meg a számok). Tekintsük meg a programot egyben, néhány apró dologgal kiegészítve a megjelenés miatt: print " "; for ($x=1; $x<=10; $x++) { if ($x < 10) {$x = " $x";} elsif ($x < 100) {$x = " $x";} print "$x "; } $z = "-" x 41; print " $z "; for ($x=1; $x<=10; $x++) { if ($x < 10) {$x = " $x";} print "$x|"; for ($y=1; $y<=10; $y++) { $z = $x*$y; if ($z < 10) {$z = " $z";} elsif ($z < 100) {$z = " $z";} print "$z "; } print " "; } exit; A kimenet pedig: 1 2 3 4 5 6 7 8 9 10 ----------------------------------------1| 1
2 3 4 5 6 7 8 9 10 2| 2 4 6 8 10 12 14 16 18 20 3| 3 6 9 12 15 18 21 24 27 30 4| 4 8 12 16 20 24 28 32 36 40 5| 5 10 15 20 25 30 35 40 45 50 6| 6 12 18 24 30 36 42 48 54 60 7| 7 14 21 28 35 42 49 56 63 70 8| 8 16 24 32 40 48 56 64 72 80 9| 9 18 27 36 45 54 63 72 81 90 10| 10 20 30 40 50 60 70 80 90 100 Ezt, a szorzótáblát generáló programot megtalálja a cikk mellett, Perl fordító segítségével futtatható (fordító beszerezhető a www.perlcom url-en); For ciklus segítségével kiírathatjuk egy tömb összes elemét: @tomb = ("monitor","egér","nyomtató","billentyűzet"); for ($x=0; $x<@tomb; $x++) { print "$x. elem: $tomb[$x] "; } A kimenetre az alábbiak kerülnek: 0. 1. 2. 3. elem: elem: elem: elem: monitor egér nyomtató billenty?zet Ha egy tömböt skalárként szerepeltetünk, akkor az elemek számát fogja visszaadni, ezt használtam ki a példánál, ahol a kifejezés $x<@tomb volt. Lista
készítésénél remekül használható az $x változó az utasításblokkon belül, mint ahogy a példában az bemutatásra került A for ciklus másik fajtája a foreach ciklus, mely egy értéklistán megy végig, például egy tömbön, és minden értéket egy változóba másol. Tekintsük meg egy, az előzőhöz hasonló példát a foreach ciklus használatával! @tomb = ("monitor","egér","nyomtató","billenty?zet"); print "A tömb elemei:"; foreach $elem (@tomb) { print " $elem;"; } A kimenetre pedig az alábbi sor kerül: A tömb elemei: monitor; egér; nyomtató; billentyűzet; Talán emlékszik még a Hash-eknél tárgyalt keys illetve values operátorra. Ezeket leggyakrabban a foreach ciklusnál használjuk, az alábbi példa egy Hash elemeit listázza ki: %sokminden = ( "gyümölcs" => "alma", "virág" => "rózsa", "madár" => "gólya");
foreach $kulcs (keys %sokminden) { print "$kulcs: $sokminden{$kulcs} "; } És a kimenet: virág: rózsa gyümölcs: alma madár: gólya Most pedig essen szó néhány programszerkezeteknél használható operátorról. Ilyen például a redo operátor, mellyel a kifejezéstől függetlenül még egyszer végrehajtódik az utasításblokk. Tekintsen meg egy példát! @szamok = (1.10); foreach $szam (@szamok) { $szam++; if ($szam < 20) { redo; } print "$szam "; } Itt egy tömböt hoztam létre, melynek elemei 1, 2, 3, . 9, 10 - a tartományoperátort használtam, melyről az első számban már volt szó Aztán egy foreach ciklussal mentem végig az értékeken, és mindet növeltem eggyel. Aztán megvizsgáltam, és ha értéke kisebb volt, mint 20, akkor a redo segítségével itt megszakítottam az utasításblokkot, és előröl kezdtem, tehát, megint növeltem az értéket. Ha az érték már 20 volt, akkor az if elágazásban a kifejezés már nem volt igaz
($x < 20), ezért az érték kiírásra került. Ez a folyamat zajlódott le az összes tömbelemnél, így tíz db 20-as került a kimenetre. A redo segítségével ciklusokat is készíthetünk egyszerű utasításblokkokból: $szam = 1; { if ($szam < 10) { $szam++; redo; } } print "$szam értéke: $szam"; Egy utasításblokkot készítettem, melyben megvizsgáltam $szam értékét, és ha kisebb volt, mint 10, akkor növeltem az értékét, és újra végrehajtattam az utasításblokkot. A kimenetre ez került: $szam értéke: 10 Nézzünk meg egy másik operátort, mégpedig a next-et, mely arra való, hogy az utasításblokk végrehajtását megszakítsuk, és azonnal a következő ciklusra ugrojunk. Nézzünk meg egy ciklusszerkezetet, mely felsorolja egy tömb elemeit, kivéve a második elemet: @tomb = ("processzor","alaplap","memória","háttértár","meghajtó"); for ($x=0; $x<@tomb; $x++) { if ($x eq 1) {
next; } print "$tomb[$x] "; } A kimenet: processzor memória háttértár meghajtó Mint látható, a tömb elemei felsorolásra kerültek, kivéve a második elemet, mivel ha az aktuális tömbelem számozása 1 volt, akkor a next utasítás hajtódott végre, ergo kimaradt a tömbelemet kiíró sor. Most pedig essen szó a last operátorról, mely hasonló a next-hez, mivel itt is ugyanúgy megszakítja az utasításblokk végrehajtását, csak itt nem a következő ciklusra ugrasztja, hanem teljesen kilépteti a ciklusszerkezetből a program futását. Tekintse meg a fenti példát a last használatával! @tomb = ("processzor","alaplap","memória","háttértár","meghajtó"); for ($x=0; $x<@tomb; $x++) { if ($x eq 1) { last; } print "$tomb[$x] "; } És a kimenet pedig: processzor A program futása hasonlóképpen zajlott az előzőhöz, viszont itt a második elem átugrása után végleg elhagyta a
ciklusszerkezetet. Most pedig a cikksorozat harmadik számának a végére értünk, remélem, ezzel sikerült elsajátítania a programszerkezetek használatát. A mintaillesztést a következő számra hagyom, addig is minden jót! 4. Mintaillesztések A múltkori számban a különböző ciklusszerkezetek kerültek ismertetésre, most a Perl egyik legkifejlettebb része, a mintaillesztés van soron. Mit is nevezünk így? Egyszerű dolgokat már Ön is biztosan használt, például file-ok keresésekor, vagy kilistázásakor. Például txt file-ok keresésénél az alábbi formát használtuk: *.txt A filenevet adtuk így meg, mely itt most egy tetszőleges karakterekből álló, és tetszőleges hosszúságú [lehet akár 0 hosszú is] karaktersorozatból, majd egy .txt végződésből áll Ha az a betűvel kezdődő file-okat szeretnénk kilistázni, akkor azt így tesszük: a*.txt Talán a fenti szituációhoz tudnám hasonlítani a Perl mintaillesztését, persze annál sokkal
bonyolultabb rendszerről van szó. Perl-ben is meg kell adni egy mintaszöveget, mely a program futása során összehasonlításra kerül a megvizsgálandó szöveggel, és amennyiben az illesztés sikeres, a visszatérési érték true, ellenkező esetben pedig false. Ezt nevezzük szabályos kifejezésnek A mintát / jelekkel határoljuk, tekintsen meg egy egyszerű példát! @elemek = ("processzor","egér","nyomtató","monitor","alaplap"); foreach $elem (@elemek) { if ($elem =~ /o/) { print "$elem "; } } Egy tömb került létrehozásra, majd az elemeiből egy foreach ciklussal végigmenve, egy elágazás segítségével a program kiírta az o betűt tartalmazó sztringeket. Ezek után már biztosan Ön is kitalálhatta a program kimenetét: processzor nyomtató monitor A minta megadásánál használható egy előre megadott változó is: @elemek =
("asztal","ablak","Jakab","darabka","színház"); $minta = "ab"; foreach $elem (@elemek) { if ($elem =~ /$minta/) { print "$elem "; } } A példa hasonló az előzőhöz, viszont itt a tömb létrehozása után egy $minta változó került dekralárásra, majd a változót, mint mintát hasonlítottuk össze a tömb elemeivel, az eredmény: ablak Jakab darabka Jól látható, hogy mindegy, hogy a minta [ez esetben az ab] a vizsgált sztring elejére, valahol a közepére, vagy a végére illeszthető, a kiválasztás mindenképpen megtörténik. Azonban ez nem biztos, hogy minden esetben cél Ha azt szeretnénk, hogy a minta csak a vizsgálandó szöveg elejéhez kerüljön rögzítésre, akkor a minta elé egy ^ jelet kell tenni: @elemek = ("asztal","kapu","alma","inga","fal"); foreach $elem (@elemek) { if ($elem =~ /^a/) { print "$elem "; } } Ebben az
esetben csak az olyan tömbelemek kerülnek kiírásra, amelyek első karaktere egy a betű. Ha a minta [ebben az esetben az a] csak a sztring végéhez, vagy valamelyik belső karakteréhez illeszkedik, a kiválasztás nem történik meg. Ezek után nem meglepő a kimenet: asztal alma A ^ párja a $, melynek ugyanaz a feladata, csak nem a szöveg elejéhez, hanem a végéhez próbálja illeszteni a mintát: @elemek = ("asztal","kapu","alma","inga","fal"); foreach $elem (@elemek) { if ($elem =~ /a$/) { print "$elem "; } } Ebben az esetben pedig csak az a-ra végződő sztringek kerülnek kiválasztásra: alma inga Ugyanezt karaktersorozatokkal is meg lehet csinálni, a illetve a B segítségével, ezek az egész mintára vonatkoznak, tehát például a /keszit/ kiválasztja a "keszit" vagy az "elkeszit" sztrignet, viszont a "keszito"-t mar nem. Ennek ellentétje a B, amely /keszitB/ esetén
kiválasztja a "keszito"-t, viszont a "keszit" -et vagy a "elkeszit"-et mar nem. Egymás után többször szereplő karakterekre is lehet keresni, ebben az esetben a keresni kívánt karakter végére egy * jelet kell tenni. Az alábbi példa 0 vagy több l betűt keres: @elemek = ("üveg","gally","alma","autó","fal"); foreach $elem (@elemek) { if ($elem =~ /l*/) { print "$elem "; } } A kimenet: üveg gally alma autó fal Bizonyára Ön is észrevette, hogy a * jelnek ebben a formában nincs túl sok haszna, mivel az akárhány karakterbe beletartozik a nulla karakter is, ergo azok az elemek is, amelyekben nulla darab l betű van. Hasonló hatása van a + karakternek is, azonban ez egy vagy több karaktert keres. A *-ot +-ra változtatva már legalább egy l betű szükséges a kiíráshoz: @elemek = ("üveg","gally","alma","autó","fal");
foreach $elem (@elemek) { if ($elem =~ /l+/) { print "$elem "; } } A kimenet: gally alma fal Használhatjuk a ? kifejezést is, mely az előtte szereplő karakterből nullát vagy egyet keres. A * jelhez képest ebben a példában nincs túl sok értelme [hiszen ugyanazt az eredményt adja], viszont a későbbiekben használni fogjuk. Az eddig említett sokszorozókon kívül a Perl rendelkezik egy ún. általános sokszorozóval, mely arra való, hogy egy meghatározott tartományon belüli számszor ismétlődő mintát keresünk. A tartományt kapcsos zárójelekkel jelöljük, benne a két szélső értéket vesszővel elválasztva: @elemek = ("gally","alma","áll","autó","fal","galllly"); foreach $elem (@elemek) { if ($elem =~ /al{2,4}/) { print "$elem "; } } Elnézést az értelmetlen szóért, de három egymás után következő l betűt tartalmazó szó nem hinném, hogy van. :) A sztringekben
egy a, és utána 2, 3, vagy 4 l betűt kerestünk. Az eredmény: gally galllly Az "áll" tömbelem azért nem szerepel itt, mert ugyan a két l betű megtalálható benne, viszont előttük nem "a" szerepel. Az általános sokszorozó esetén nem csak tartományokat, hanem fix értékeket is meg lehet adni, vagy akár csak minimumot, vagy csak maximumot. Az alábbi példa öt darab a betűt keres: @elemek = ("van","vaaan","vaaaaan"); foreach $elem (@elemek) { if ($elem =~ /a{5}/) { print "$elem "; } } Az eredmény, mivel csak ebben az egy szóban szerepelt egymás után 5 db a betű: vaaaaan Ha a sokszorozó használatánál minimum értéket szeretne megadni, azonban maximumot nem, akkor egyszerűen hagyja el a második számot, például így: /a{5,}/ Ez öt, vagy több a betűt keres. Az alábbi viszont pont az ellentéte: /a{,5}/ Ez pedig öt, vagy annál kevesebb a betűt keres. A következő mintaillesztésnél
használható operátor a pont (.), mely egy tetszőleges karaktert jelöl Tekintsen meg egy ide kapcsolódó példát! @elemek = ("van","vn","vaan"); foreach $elem (@elemek) { if ($elem =~ /v.n/) { print "$elem "; } } Egy v betűt, majd egy [azaz nem több, és nem is kevesebb] tetszőleges karaktert, és egy n betűt keresett a program. Itt csak a van került kiválasztásra, mivel a "vn" -ben szerepel a v és az n, viszont nincs köztük semmilyen karakter, a "vaan" esetében pedig két darab karakter is található a v és az n között, ezért nem került kiválasztásra. Mint látható, a . operátor akármilyen karaktert elfogadott Létrehozhatunk viszont karakterosztályokat, szögletes zárójelek segítségével, ekkor csak az itt megadott karaktereket engedélyezünk az adott helyen. Például az alábbi karakterosztály magába foglalja a számokat 0-9ig: [0123456789] Persze ha több, egymás után következő
számot vagy betűt akarunk megadni, akkor lehetőség van rövidítésre, mivel itt is használható egy tartományoperátor: [0-9] Ennek ugyanaz a hatása, mint a fenti sorozatnak. Persze ugyanezt megcsinálhatjuk betűkkel is: [a-z] És egy zárójelen belül akár keverhetünk számokat, betűket, és nagybetűket is: [a-zA-Z0-9] A fenti karaktercsoport egyenértékű az alábbival: [abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789] Tekintse meg, hogyan működik ez a gyakorlatban! @elemek = ("asztal","ASZTAL","aSzTaL"); foreach $elem (@elemek) { if ($elem =~ /[a-z]/) { print "$elem "; } } A kimenetre a következők kerülnek: asztal aSzTaL Mint látható, kisbetűket próbáltunk illeszteni, mely az első tömbelemnél sikeres volt, viszont a második csak nagybetűket tartalmazott, ezért az illesztés hamis értéket adott, az utolsó tömbelem pedig vegyesen tartalmazott kis és nagybetűket egyaránt, tehát az illesztés
itt is sikeres volt. Tekintsen meg egy példát, mely különválasztja a szavakat és a számokat: @elemek = ("autó","3","bogár","354","7465","TÉVÉ","54"); foreach $elem (@elemek) { if ($elem =~ /[a-zA-Z]{2,}/) { print "$elem - szó "; } elsif ($elem =~ /[0-9]/) { print "$elem - szám "; } } A kimenet: autó - szó 3 - szám bogár - szó 354 - szám 7465 - szám TEVE - szó 54 - szám Vannak olyan karaktercsoportok, amelyek már alapban szerepelnek a Perl-ben, ilyenek például: d D w W s S szám nem szám szó nem szó üres karakter [pl. szóköz, tabulátor, sortörés, stb] nem üres karakter Ha például szavakat szeretnénk keresni, akkor elég a w -t használni, mely egyenértékű a [a-zA-Z0-9 ] karaktercsoporttal. Mint látható, a "szó"-ba beletartoznak a számjegyek, és az aláhúzás karakter is ( ). A d pedig egyenértékű a [0-9]-el, tehát például számokat
az alábbi módon is kereshet: /d/ Mintaillesztésnél lehetőség van zárójelek használatára, mely segítségével egy mintarészletet eltárolhatunk, és a minta későbbi részében hivatkozhatunk rá egy jel és számok segítségével annak megfelelően, hogy hányadik eltárolt karakterről van szó. Például: @elemek = ("-b-c-d-b-c-d","-d-k-g-h-k-g","-z-f-g-z-f-g"); foreach $elem (@elemek) { if ($elem =~ /-(.)-()-()-1-2-3/) { print "$elem "; } } A kimenet: -b-c-d--b-c-d -z-f-g--z-f-g Ez a példa 6 db kötőjelekkel elválasztott betűt keres, a feltétel csak annyi, hogy az első három betűnek ugyanolyannak kell lennie, mint a második három betűnek. Ezért van az, hogy a -b-c-d-b-c-d és a -z-f-g-z-f-g megfelelő volt, viszont a -d-k-g-h-k-g már nem Most pedig essen szó az s paraméterről. Tekintsen meg egy példát, amelyben egy többsoros változó szerepel! $tobbsoros = "elso sor masodik sor harmadik sor";
if ($tobbsoros =~ /sor./) { print "igaz"; } else { print "hamis"; } A skalár létrehozása után megvizsgáltuk, hogy van-e benne olyan karaktersorozat, hogy "sor", és hogy szerepel-e utána egy bármilyen más karakter. Mint látható a skaláron, három helyen is szerepel benne a "sor", viszont kétszer sortörés van utána, egyszer pedig a változó végén áll. Ezért a program azt fogja kiírni, hogy "hamis" Az alábbi példa az s paraméter használatát mutatja be: $tobbsoros = "elso sor masodik sor harmadik sor"; if ($tobbsoros =~ /sor./s) { print "igaz"; } else { print "hamis"; } A lezáró / jel után egy s betűt írtam, így már a program azt írta ki, hogy igaz. De miért? Azért, mert az s paraméter használatával a sztringet a Perl egy sorként fogja tekinteni, azaz így már az első két helyen, ahol előfordul a "sor", már következni fog valami utána, így a kifejezés
true értékkel tér majd vissza. Végül még egy apróság, a levédő karakter, vagyis a jel, mely ebben az esetben is használható, ha például a mintában / jelet szeretnénk alkalmazni, akkor ezt le kell védeni a jellel [tehát így: /], ellenkező esetben a Perl azt hiszi, hogy vége van a mintának. Mivel a jelnek is speciális szerepe van, ha a mintában használni szeretné, ezt is le kell védeni önmagával, azaz \-t kell írni, ugyanígy a [, %, &, ^, $, stb. karakterekkel, amelyek valamilyen funkcióval rendelkeznek. A következő részben szó lesz a helyettesítésről, és néhány ide kapcsolódó operátorról. Addig is minden jót! 5. Helyettesítések, kimenetgenerálás A Perl programnyelvet tárgyaló sorozatunk az ötödik részéhez érkezett. A negyedik részben megismerkedhetett a mintaillesztéssel, most folytatva a témakört, különböző helyettesítéseket fogunk végezni a szövegeken, majd néhány ide kapcsolódó operátorról lesz szó.
Ezek után megismerkedhet a különböző bemenetekkel - input - illetve kimenetekkel - output -, formázott szöveg kiírásával, stb. CGI scriptek írásakor az egyik legelterjedtebb nyelvet, a Perl programnyelvet tárgyaló sorozatunk az ötödik részéhez érkezett. A negyedik részben megismerkedhetett a mintaillesztéssel, most folytatva a témakört, különböző helyettesítéseket fogunk végezni a szövegeken, majd néhány ide kapcsolódó operátorról lesz szó. Ezek után megismerkedhet a különböző bemenetekkel - input - illetve kimenetekkel - output -, formázott szöveg kiírásával, stb. Helyettesítések segítségével egy mintát kereshetünk egy sztringben, találat esetén pedig a mintát egy másikra cserélhetjük. Egy egyszerű példa: $betuk = "abcd"; $betuk =~ s/a/b/; print $betuk; A program a skalárban az "a" betűt b-re cserélte, a helyettesítés szintaktikája tehát: s/helyettesítendő minta/mire cserélje/ Ezek után
nem meglepő, hogy a kimenetre "bbcd" kerül. Mivel itt is mintaillesztésről van szó, használhatók a múlt számban ismertetett mintaillesztésnél használatos operátorok is, az alábbi program például a sztringben lévő számot és az előtte szereplő karaktert felcseréli: $sztring = "abcde9fgh"; $sztring =~ s/(.)([0-9])/$2$1/; print $sztring; A program kiírja, hogy abcd9efgh. A mintánál egy tetszőleges karaktert adtam meg, melyet egy szám követ, persze mind a kettőt zárójelek között, így a talált karakterek eltárolódtak a $1 és a $2 változóba, így a helyettesítő sztring helyére csak ezt a két változót adtam meg felcserélve ($1$2). Az előző részben volt szó a sokszorozókról, a {n,k}-re gondolok, amelynél a k értékének [tehát hogy maximum hány karaktert keressen] eddig nem vettük sok hasznát, mivel ha többször szerepelt a karakter, mint a k értéke, akkor is sikeres volt a mintaillesztés. Azonban amikor
szövegrészeket is kicserélünk, akkor már van értelme a k megadásának, ezt egy rövid példával szeretném szemléltetni: $a = "haaaaaah"; $a =~ s/a{3,}/i/; print $a; $b = "haaaaaah"; $b =~ s/a{3,4}/i/; print $b; $a és $b értékében hatszor szerepel egymás után az a betű. Először egy mintaillesztést hajtunk végre, amelyben ha háromszor, vagy többször szerepel az "a", akkor lecseréljük azokat egy "i"-re. Ekkor a program kiírta, hogy hih, mivel nem adtunk meg maximális értéket a keresendő a betűk számának a megadásakor. Másodjára, $b eseténél viszont már megadtam maximális értéknek a 4-et, így a kimenetre az került, hogy "hiaah", mert az illesztés maximum 4 db a betűre vonatkozik. A következő példa a szavak közti szóközök számát egyre csökkenti, amennyiben több is megtalálható: $a = "több szóköz a szavak között"; $a =~ s/ +/ /g; print $a; A sokszorozót (ez esetben
a + jelet, de lehetett volna {1,} vagy {2,} is) ez esetben egy szóköz után tettem, így a program az egyszer, vagy egynél többször szereplő szóközöket kereste. A kimenet: több szóköz a szavak között Az alábbi opciók használhatók a helyettesítő operátornál: g - többször is illeszt i - nem különbözteti meg a kis és a nagybetűket Egy példa: $a = "haaaaaa"; $a =~ s/a/i/; print $a; $b = "haaaaaa"; $b =~ s/a/i/g; print $b; A kimenetre először az kerül, hogy hiaaaaa, másodjára az, hogy hiiiiii. Mint látható, alapértelmezésben a Perl csak egyszer végzi el az illesztést, azonban a g opció használatával már az összes "a" betűt lecserélte. Példa az i használatára: $a = "kisbetuNAGYBETU"; $a =~ s/un/u N/; print $a; $b = "kisbetuNAGYBETU"; $b =~ s/un/u N/i; print $b; Ebben az esetben $a és $b olyan értéket kapott, amelyben szerepel kis- és NAGY betű is. Az elő esetben csak a szokásos
módon hajtottuk végre a cserét, ezért a minta nem volt illeszthető, mivel a Perl különbséget tesz a kis- és a nagy betűk között [Az "un" nem egyenértékű "uN" -el], ezért a kimenetre "kisbetuNAGYBETU" került. A második esetben, az i funkció használatával a Perl már egyenértékűnek vette az "un"-t az "uN" -el, így végrehajtotta az illesztést, a kimenetre itt már "kisbetu NAGYBETU" került. A fent látott opciók kombinálhatók is egymással: $a = "kisbetuNAGYBETUkisbetuNAGYBETU"; $a =~ s/un/u N/i; print "$a "; $b = "kisbetuNAGYBETUkisbetuNAGYBETU"; $b =~ s/un/u N/ig; print $b; A kimenet: kisbetu NAGYBETUkisbetuNAGYBETU kisbetu NAGYBETUkisbetu NAGYBETU Először csak az i opciót használtam, ezért a csere csak egyszer történt meg, viszont másodjára már kibővítettem a g-vel, így a csere a minta mindkét előfordulási helyén megtörtént. Most pedig két
operátorról lesz szó, amelyek alkalmazása során szintén mintákat használunk, ezek a join és a split. A split operátor segítségével egy sztringet több részre darabolhatunk fel, egy minta segítségével: $gymumolcsok = "alma;narancs;barack;meggy;ananász"; ($a, $b, $c, $d, $e) = split(/;/, $gymumolcsok); print "$b"; Egy skalár került létrehozásra különböző szavakat tartalmazva, melyek egy ;-vel voltak elválasztva. Ez után következett a szóban forgó split operátor használata, először a változók neveit kell megadni, amelyekbe az egyes elemek eltárolódnak, majd ezeknek értékként a split-et adni, melyben először a mintát kell leírni, majd a szétvagdosni kívánt változót. Ekkor a felsorolt változókba fognak kerülni a szétvagdosásnál keletkezett darabok. Ezek után már gondolom, hogy Ön is rájött, a kimenetre a "narancs" került Az $a, $b, $c, stb helyett tömb is megadható, ekkor a darabok a tömb
egy-egy elemei lesznek: $gymumolcsok = "alma;narancs;barack;meggy;ananász"; (@gyumolcsok) = split(/;/, $gymumolcsok); print $gyumolcsok[1]; A kimenet szintén a "narancs". A másik operátor, a join hasonlóan működik, mint a split, csak épp az ellentétét csinálja, a megadott minta alapján sztringeket fűz össze. Bővítsük ki a fenti példát a join-al! $gymumolcsok = "alma;narancs;barack;meggy;ananász"; ($a, $b, $c, $d, $e) = split(/;/, $gymumolcsok); $egybefuzve = join(, , $a, $b, $c, $d, $e); print $egybefuzve; Ami ezt írja ki: alma, narancs, barack, meggy, ananász Mint látja, a join operátornak először meg kell adni, hogy mit írjon a változók tartalmai közzé, majd felsorolni a változókat, amelyeket egybe kíván fűzni. Persze a fenti példát sokkal egyszerűbben megoldhattuk volna egy cserével :) Nem csak változókat, hanem egy, vagy akár több tömb elemeit is egybefűzhetjük: @a =
("virág","ház","autó"); @b = ("madár","újság"); $egybefuzve = join(, , @a, @b); print $egybefuzve; A fenti kódrészlet az alábbiakat írja ki: virág, ház, autó, madár, újság Ezzel a végére értünk a témakörnek, most egy új következik, név szerint az input és az output. Talán Ön is találkozott már ezekkel a szavakkal, magyarul kimenetet illetve bemenetet jelentenek. Egy hétköznapi példához tudnám ezt hasonlítani, például egy hi-fi toronyhoz Azon is található legalább egy bemenet, amin keresztül más eszközöket csatlakoztathatunk hozzá, például mikrofont, vagy a számítógép hangkártyáját, és van rajta kimenet, amin általában a hangfalak vannak. A Perl-ben egy bemenet, és egy kimenet található, melyeket úgynevezett filekezelőkkel különböztetünk meg: STDIN és STDOUT [standard input és standard output]. A cikk többi részében ezekről lesz szó. Az STDIN arra szolgál,
hogy a felhasználótól adatokat kérhessünk be. Ehhez az úgynevezett kapocsoperátor segítségével kell hozzárendelnünk a filekezelőt egy változóhoz. Ekkor a billentyűzetről olvasunk be adatokat $adatok = <STDIN>; Hasonló sorral már találkozhatott az első fejezetben is. Ekkor a program addig olvassa be az adatokat, amíg a felhasználó le nem üti az enter billentyűt, ekkor folytatódik a program futása. Ezért van az, hogy az így kapott változó tartalmaz egy sortörést is, erre van egy függvény, mégpedig a chomp, amely ezt eltávolítja. Egy példa, amely jól mutatja ezt: $szoveg a = "A mondat után sortörés van. "; print $szoveg a . "[vége]"; print " "x3; $szoveg b = "A mondat után sortörés van, melyet levágunk. "; chomp($szoveg b); print $szoveg b . "[vége]"; Mint látható, létrehoztunk egy sztringet, melyet sortörés zár, majd kiírtuk, a végén egy "[vége]" felirattal.
Aztán megismételtük ugyanezt, csak a kiírás előtt a chomp-al levágtuk a sortörést, így a kódrészlet kimenete: A mondat után sortörés van. [vége] A mondat után sortörés van, melyet levágunk.[vége] Jól látható, hogy az első résznél a "[vége]" már a következő sorba került, ezzel ellentétben a második esetben még ugyanabba a sorba írta ki. E kis kitérő után folytatnám a standard bemenet ismertetését. Tekintsen meg egy programot, mely mindaddig adatokat olvas be, amíg a felhasználó be nem írja, hogy "vege": while ($adatok = <STDIN>) { if ($adatok eq "vege ") { last; } else { $osszes adat .= $adatok; } } A while ciklus remekül használható erre a célra, mivel mindaddig fut, amíg a kiértékelés igaz. Nem használtam a chomp-ot, ezért kiléptető szöveg egészen pontosan a "vege ", tehát sortöréssel együtt, ha a felhasználó ezt írja be, a last hajtódik végre [a ciklusszerkezeteket
tárgyaló részben volt róla szó, a ciklusból való kilépésre szolgál], ellenkező esetben a beírt adat hozzáfűződik egy skalárhoz. Mivel nem vágtam le a sortöréseket, a skalárban [$osszes adat] a sortörésekkel együtt fog szerepelni a szöveg. Most pedig beszéljünk a kapocsoperátor segítségével beolvasható input-ról. Ekkor a program futtatásánál a parancssorban meg kell adni fileok neveit, ekkor az adatokat a program a file-okból fogja beolvasni A filekezelőt ebben az esetben nem kell megadni, csak a kapocsoperátor két tagját (<>). Egy egyszerű példa: A pelda.pl tartalma: while (my $sor = <>) { print $sor; } exit; Az adatok.txt tartalma: A XIX. század első felében Magyarországon is a kibontakozó romantika határozta meg az irodalom jellegét. A parancssorba a következőt írtam: perl pelda.pl adatoktxt A program pedig kiírta: A XIX. század első felében Magyarországon is a kibontakozó romantika határozta meg az irodalom
jellegét. A példa megtalálható mellékelve, pelda-1.pl ill adatoktxt néven A file-ok nevei [a fenti esetben csak az adatok.txt] a @ARGV tömbbe kerülnek, az alábbi program például kilistázza a kapott file-ok neveit [az elválasztásra a nemrég megismert join operátort használjuk]: $filenevek = join(, , @ARGV); print $filenevek; Hívjuk meg a programot az alábbi módon! perl pelda-2.pl elsotxt masodiktxt harmadiktxt Melynek hatására az kiírja: elso.txt, masodiktxt, harmadiktxt A példa megtalálható mellékelve, pelda-2.pl néven A @ARGV tömb akár meg is változtatható, így anélkül is olvashatunk be file-okat, hogy a futtatáskor megadnánk a filenevet. Persze file-ok beolvasására általában nem ezt a módszert használjuk, a filekezelés a következő rész témája lesz. A standard bemenet tárgyalása után következzen a standard kimenet [STDOUT]. A kimenet készítésére szolgáló függvény már biztosan ismeri, hiszen az első példaprogramban
["Hello World!"] is használtuk. Igen, a print-ről van szó A standard kimenet az alapértelmezett, ezért az alábbi két sor egymással egyenértékű: print "Helló Világ! "; print STDOUT "Helló Világ! "; Itt jegyezném meg, hogy ha hosszabb, több sortörést tartalmazó szövegeket jelenítünk meg, akkor használható az alábbi forma: print <<VEGE; Több soros szöveg, levédés nélkül használhatók "idézőjelek", használata egyszer?bb. VEGE Mint látható, a print után két < jelet követően egy szöveget kell megadni [pontosvessző zárja a sort itt is]. Az itt megadott szöveget kell majd még egyszer megadnunk, ez fogja a Perl-nek a megjelenítés végét jelezni. A kimenetre a szöveg kerül, sortörésekkel együtt Mint az a példában is olvasható, nem kell bajlódnunk a és " karakterek levédésével, azonban a tömbök illetve változók beszúrása itt is megtörténik, ezért a $, @, és %
karakterekkel már óvatosan kell bánni. Még egy gyakori hibára hívnám fel a figyelmet, mégpedig arra, hogy a megjelenítést lezáró tagnak [ebben az esetben a "VEGE"] önállóan kell szerepelnie egy sorban, különben a megjelenítés nem fog lezáródni, és természetesen a megjelenítendő szövegben sehol sem szabad szerepelnie önállóan egy sorban. Néhány példa: print <<VEGE; Ez a mondat tartalmazza a VEGE szót külön sorban, ezért hibaüzenet fogunk kapni. VEGE print <<VEGE; Ez a mondat viszont rendben meg fog jelenni, mert a VEGE szó nem önálló sorban van. VEGE print <<VEGE; A lezáró tag után nem kell pontosvessző, mert probléma lesz belőle. VEGE; print <<VEGE; Ebből is probléma lesz. VEGE Végül a megformázott kimenetről ejtenék néhány szót. Ebben az esetben a kiírásnál a print helyett a printf függvényt használjuk, melynek két paramétert kell megadni, a formázó utasításokat, valamint magát
a kimenetet. A formázó utasításban két dolgot határozunk meg, először a kimenet típusát [pl. sztring, lebegőpontos szám, stb], majd a mező méretét, amelyben a sztring kiírásra kerül [ergo a sztring szóközökkel töltődik fel, így éri el a kívánt hosszt]. Az egyszerű sztring jele az s, az alábbi példában egy 10 karakter hosszú kimenetet fogunk készíteni egy sztringből: printf "%10s", "szöveg"; A kimenet ez lesz: szöveg Mint látható, a skalár tartalmának hossza rövidebb volt, mint a formázásnál megadott szám, így szóközökkel feltöltésre került. Ha a hossz nagyobb vagy egyenlő, mint a megadott szám, akkor a fent látott formázásnak nem lesz hatása, mivel a Perl automatikusan beállítja a megfelelő értéket: printf "%10s", "hosszabb szöveg"; A kimenet: hosszabb szöveg Számokat a d segítségével írunk ki: @szamok = (3,54,436.43,123891,34,9861); foreach $szam (@szamok) { printf
"%5d", $szam; print " "; } Ebben az esetben a kimenet: 3 54 436 123 3 986 Mint látható, a nem egész számoknál a tizedespont utáni rész nem került kiírásra. Azonban a fenti példa az f segítségével már máshogy mutat [ebben az esetben meg kell adni a tizedesjegyeknek szánt hely hosszát is]: @szamok = (3,54,436.43,123891,34,9861); foreach $szam (@szamok) { printf "%8.3f", $szam; print " "; } A kimenet így már pontos, és igen rendezett, mivel az f használatakor a Perl a tizedespontokat egy vonalba rendezi: 3.000 54.000 436.430 123.891 3.400 986.100 A példában a hossznál megadott 8.3 számból a 8-as a kiírt sztring egészére érvényes, melyből a tizedespont utáni számjegyek kapják majd a 3 karakter hosszúságú mezőt. Azonban ez mindenképpen 3 karakter marad, akárhány számjegyből áll a szám, a Perl itt már nem fogja meghosszabbítani a mezőt: @szamok = (5.423435, 832435, 67896473, 3564654747, 100874);
foreach $szam (@szamok) { printf "%8.3f", $szam; print " "; } A kimeneten a számok tizedesjegyeinek a száma 3-ra korlátozódik: 5.423 8.324 67.896 356.465 1.009 Akár exponenciális számokat is képezhetünk, az e segítségével: @szamok = (3,54,436.43,123891,34,9861); foreach $szam (@szamok) { printf "%8.3e", $szam; print " "; } A kimenet: 3.000e+00 5.400e+01 4.364e+02 1.239e+02 3.400e+00 9.861e+02 Most pedig a cikk zárásaként készítsünk el egy rövid programot, amelyben feljegyzéseket készíthetünk. A program szubrutinokból fog állni, lesz egy menü szubrutin, egy olyan, amellyel a feljegyzéseket lehet készíteni, egy olyan, amellyel törölni lehet, és egy olyan, amellyel meg lehet tekinteni, valamint a programból ki is lehet majd lépni. &menu; exit; sub menu { print "-"x37; print " (u)j, (t)örlés, (m)egtekintés, (v)ége "; print "-"x37; print " "; $mit =
<STDIN>; chomp($mit); Mint látható, egy nagyon rövid programról van szó, hiszen csak a menu nevű szubrutin hívódik meg benne, melyben kiírjuk a választható lehetőségeket, majd bekérjük, hogy a felhasználó melyiket választja. Ezután elágazások következnek a különböző szubrutinokra: if ($mit eq "u") { &uj; } elsif ($mit eq "t") { &torol; } elsif ($mit eq "m") { &megt; } elsif ($mit eq "v") { print "Ha kilép, törlődnek a feljegyzések! Valóban ki szeretne lépni? [i/n] "; $valoban = <STDIN>; chomp($valoban); if ($valoban eq "i") { exit; } else { &menu; } } else { &menu; } } Attól függően, hogy a felhasználó mit üt be, meghívódik valamelyik szubrutin, kivéve, ha a v betűt írják be, mert ez a kilépést jelenti. Ekkor a program rákérdez, hogy valóban ki akar-e lépni a felhasználó, és ha igen, akkor az exit paranccsal egyszerűen megszakad a program
futása, minden más esetben a menü hívódik meg. Ezzel a menu szubrutin végére értünk Most nézzük meg a többi szubrutint sub uj { print "Adja meg feljegyzését! "; $uj = <STDIN>; chomp($uj); push(@feljegyzesek,$uj); print "Feljegyzés hozzáadva. "; &menu; } Ez egy egyszerű alprogram, a beütött karaktereket egy változóban tárolja, majd egy tömbhöz teszi hozzá. Most következik a megjelenítésre használt szubrutin: sub megt { print "Eddigi feljegyzések: "; foreach $feljegyzes (@feljegyzesek) { print "$feljegyzes "; } print " "; &menu; } Ez pedig egy foreach ciklus segítségével kilistázza a @feljegyzesek tömb elemeit, majd megjeleníti a menüt. Most jön a legérdekesebb szubrutin, a törlés. sub torol { print "Eddigi feljegyzések: "; for ($x=0; $x<@feljegyzesek; $x++) { $y = $x + 1; print "$y. $feljegyzesek[$x] "; } print " Hanyas számút szeretné törölni?
"; $torles = <STDIN>; chomp($torles); Egy for ciklus segítségével listázzuk ki az elemeket, így minden elem elé oda tudjuk írni a számát. Az első elemhez a 0 tartozna, ezért egy másik változót hoztam be [$y], melynek értéke mindig eggyel nagyobb, mint az $x, így a megjelenítésnél a számozás 1-el fog kezdődni. Ez után bekérjük, hogy hányas számú feljegyzést szeretné törölni. Most pedig távolítsuk el a megadott számú feljegyzést a tömbből! for ($x=0; $x<@feljegyzesek; $x++) { $y = $x + 1; if ($y ne $torles) { push(@ideigl tomb, $feljegyzesek[$x]) } } @feljegyzesek = @ideigl tomb; @ideigl tomb = (""); print "Törölve. "; &menu; } Megint elkészítjük a sorszámozott megjelenítésnél használt for ciklust, és ha a beütött szám nem egyezik a tömb aktuális elemének a számával, akkor hozzácsatoljuk egy ideiglenes tömbhöz [@ideigl tomb]. Tehát ebbe a tömbbe már nem fog bekerülni a törölni
kívánt elem Ezután már csak az eredeti tömb értékeit kell az ideiglenes tömb értékeire állítani, és a törlés végre is hajtódott. Mivel a push-t használtuk, az ideiglenes tömböt le kell nullázni, hogy a következő törlésnél ne maradjanak benne az előző értékek. Ez után a futás visszatér a menu szubrutinhoz, és a törlés szubrutin lezárul. A példaprogram megtalálható mellékelve, pelda-3pl néven Ezzel az ötödik rész végére értünk, a következő részben szó lesz file-ok kezeléséről, különböző fájlvizsgálatokról. A cikkben szereplő forráskódok ITT tölthető le! 6. Lemezműveletek és hibakezelés A Perl programnyelvet ismertető sorozat hatodik részében folytatódik az input illetve output tárgyalása, ebben a részben megismerkedhet a fájl- és könyvtárkezeléssel, ezek használatával, a különböző fájlvizsgálatokkal, fellépő hibák kezelésével, stb. Fájlkezelés Az open() függvény szolgál állományok
megnyitására; a fájlkezelőt, illetve egy paramétert kell megadni, melyben definiáljuk a fájl nevét (ha nem az adott könyvtárban van, akkor elérési úttal együtt), valamint hogy mire szeretnénk használni a fájlt (írás, olvasás, esetleg mindkettő). Az eddig látott fájlkezelők előre megszabottak voltak, azonban egy állomány esetén saját magunk választhatunk tetszés szerinti nevet, egyetlen megkötés, hogy nagy betűkkel kell írni. Az alábbi sor megnyitja az adatoktxt fájl-t: open(AKARMI, "adatok.txt"); Itt még nem jeleztük, hogy írásra, vagy olvasásra szeretnénk megnyitni, ezekhez a "<", ">", illetve "+" karaktereket kell használni, az alábbiakban felsorolásra kerülő lehetőségek vannak: open(AKARMI, "<adatok.txt"); Olvasásra nyitja meg. open(AKARMI, ">>adatok.txt"); A fájl végéhez ír, tehát az eredeti tartalom megmarad, csak hozzátoldja az új karaktereket
(append - fozzáfűzés). open(AKARMI, ">adatok.txt"); Ebben az esetben viszont felülírja az állomány teljes tartalmát, érdemes vele vigyázni! open(AKARMI, "+>adatok.txt"); Ez után lehetőség lesz írásra és olvasásra egyaránt. open(AKARMI, "+<adatok.txt"); Egyenértékű az előzővel (írás és olvasás is). A fájl megnyitása ezzel megtörtént, létrejött a fájlkezelő, melyen keresztül már hozzáférhetünk a fájl tartalmához. Miután elvégzésre kerültek a kívánt műveletek, a close() függvény segítségével lehet lezárni a megnyitott állományt, ekkor a megadott fájlkezelő is megszűnik, melyet lezáráskor meg kell adni paraméternek: close(AKARMI); A lezárás nem kötelező, mivel a program futásának vége után, vagy ha újra megnyitjuk a fájl-t, ez automatikusan megtörténik, azonban mindenképpen ajánlott, így elkerülhetők a kavarodások, és a futás is gyorsabb lesz (hiszen sokkal kevesebb
ideig marad megnyitva a fájl hosszabb programoknál erre is gondolni kell; főleg ha on-line működésre írjuk, mert így egy időben akár többször is futtathatják a programot). Az adatok beolvasásának a fájlból az egyik legegyszerűbb módja, ha egyszerűen az értékadó operátor segítségével hozzárendeljük egy skalárhoz vagy egy tömbhöz: $tartalom = <AKARMI>; @tartalom = <AKARMI>; Mint látható, a fájlkezelőt < és > karakterek közzé kell foglalni. A fenti két sor között annyi a különbség, hogy a skalárhoz való rendelésnél az éppen aktuális sort fogja a változó értékül kapni, míg tömb esetén a fájl minden sora a tömb egy-egy eleme lesz. Tehát ha a fájl tartalmát egy tömbbe szeretnénk beolvasni, majd az elemeket egyesével kiírni, az alábbi forma egy lehetséges variáció: open(FAJL, "<adatok.txt"); @adatok = <FAJL>; close(FAJL); $szamlalo = 1; foreach $sor (@adatok) { print "$szamlalo.
sor: $sor"; $szamlalo++; } Ha lefuttatjuk a kódot, az adatok.txt tartalma fog a standard kimenetre kerülni, a sorok elején jelezve számukat A kiírásnál az $sor változó mögé nem tettem sortörést, mivel a sorok végén ez már szerepel (kivéve a fájl utolsó sorát természetesen). Nem fontos tömbbe olvasni a fájl tartalmát, a fenti példa megoldható skalárba való olvasással is. Persze ha csak egyszerűen kiírnánk a változó értékét, az nem lenne célravezető, mivel mint már említettem, ekkor csak az épp aktuális sor kerül beolvasásra. Azonban miután ez megtörtént, a fájlmutató (az a pont, ahol elkezdődik az adatok beolvasása; a fájl megnyitásakor ez az első karakter) azonnal a lentebb lévő sorra ugrik. Ez után már biztosan Ön is kitalálhatta, hogy egy ciklus segítségével a fájl teljes tartalmának beolvasása egy skalárba nem nehéz feladat: addig olvassuk be az újabb és újabb sorokat, amíg azok tartanak, ergo amíg az
$sor változó valamilyen értéket kap. $szamlalo = 1; open(FAJL, "<adatok.txt"); while ($sor = <FAJL>) { print "$szamlalo. sor: $sor"; $szamlalo++; } close(FAJL); Ez a kódrészlet ugyanazt az eredményt fogja adni, mint az eggyel fentebbi. Ha fájlba szeretne írni, megnyitás után a print-tel teheti ezt meg, azonban ne felejtse el megadni a fájlkezelőt! open(FAJL, ">adatok.txt"); print FAJL "Hello world! "; close(FAJL); E kód lefuttatásakor az adatok.txt tartalma felülíródik Amennyiben a fájl nem létezik, akkor létrehozásra kerül Most nézzük meg ugyanezt úgy, hogy a fájl végéhez írunk, tehát az eredeti tartalom nem kerül felülírásra: open(FAJL, ">>adatok.txt"); print FAJL "Hello world! "; close(FAJL); Létezik egy select() függvény, ez arra szolgál, hogy az alapértelmezett fájlkezelőt megváltoztassuk. Ha sokat dolgozunk állományokkal, viszont a standard kimenetre
ritkán küldünk adatokat, akkor érdemes használni, persze ebben az esetben minden fájl megnyitásánál ugyanazt a fájlkezelőt kell használni -hacsak nincs nyitva egyszerre több állomány is. open(FAJL, ">adatok.txt"); select(FAJL); print "Ez a szöveg nem az STDOUT-ra fog íródni, hanem a fájl-ba."; close(FAJL); select(STDOUT); print "Ez viszont már a standard kimeneten olvasható."; A fájl megnyitása nyilvánvaló, hogy nem mindig sikeres, hiszen ha egy fájl nem létezik, akkor nem lehet megnyitni, például olvasásra, és írásra is csak akkor, ha az adott könyvtárban írási joggal rendelkezik a program. Unix rendszerek esetén kell erre odafigyelni A Perl engedékenysége miatt azonban futáskor ebből nem fogunk semmit sem észlelni, azaz csak azt, hogy a beolvasott adatok nem léteznek. A nyitáskor fellépő hibák kezelésére két függvény áll rendelkezésre, a die és a warn. Mindkettő esetén egy üzenetet kell
megadni, ami figyelmeztet minket, hogy a fájl megnyitása nem volt sikeres, a különbség a kettő között annyi, hogy míg a warn esetén a hibaüzenet megjelenése után folytatódik a program futása, a die esetén megszakad: open(FAJL, "<nemletezik.txt") or die "Nem sikerült megnyitni "; print "A fájl tartalma: "; while ($sor = <FAJL>) { print $sor; } Futtassuk le a programot úgy, hogy a könyvtárban ne szerepeljen "nemletezik.txt" nevű fájl Az alábbi üzenetet fogjuk kapni: Nem sikerült megnyitni. Most próbáljuk meg ugyanezt úgy, hogy a die-t warn-ra cseréljük: Nem sikerült megnyitni. A fájl tartalma: Mint látható, a warn esetén folytatódott a program végrehajtása, mivel még "A fájl tartalma:" szöveg is kiíródott. Ha az üzenet végéről elhagyjuk a sortörést, a Perl automatikusan a hibaüzenet után csatolja a futtatott program elérési útját, valamint nevét, és hogy hányadik
sorban lépett fel a hiba: Nem sikerült megnyitni. at D:My DocumentsPerl6prgpl line 1 Amit még érdemes tudni a hibakezelőkről, hogy a hibaüzenet parancssorból történő futtatás esetén úgy tűnik, mintha az STDOUT-ra menne, azonban "a látszat csal", a Perl egy külön i/o csatornát tart fenn a hibaüzenetek számára, ez az STDERR. Fájlok megnyitásánál akár már létező fájlkezelőket is felülírhatunk, ha például az alábbi sor beszúrásra kerül a program elejére, akkor a hibaüzenetek egy fájlban fognak tárolódni: open(STDERR,">hibak.txt"); Különböző vizsgálatokat is végrehajthatunk kapcsolók segítségével, ezek true-val fognak visszatérni, ha a vizsgálni kívánt tulajdonság igaz, false-al, ha nem. Nézzük a legegyszerűbb esetet, amikor azt vizsgáljuk meg, hogy a fájl létezik-e Ezt a -e kapcsolóval tehetjük meg: if (-e "fájlnev.txt") { print "A fájl létezik."; } else { print "A
fájl nem létezik."; } Néhány gyakran használt kapcsoló: -r olvasható -w írható -x futtatható -e létezik -z létezik, de üres -s étezik, és nem üres -d könyvtár Az első három paraméter a weben keresztül használt programoknál lényeges - Unix rendszereken -, segítségükkel a fájl attribútumairól szerezhetünk tudomást. Az alábbi kód például az írásra való megnyitás előtt ellenőrzi, hogy lehetséges-e, és ha nem, akkor jelzi unless (-w "adatok.txt") { print "Az állomány nem írható!"; } else { open(FAJL,">>adatok.txt"); } További információkhoz juthatunk a fájlról a stat() függvény használatával, mely több értéket is visszaad. Az egyenként történő ismertetés helyett egy rövid kódrészlet segítségével listázzuk ki ezeket a tulajdonságokat! @tulajdonsagok = stat("adatok.txt"); foreach $tul (@tulajdonsagok) { print "$tul "; } Mint látható, egy tömbben tároltam
a stat által visszaadott információkat. Persze ezeket skalárokban is tárolhattam volna, éppúgy, mint a split paraméter esetén. A kódrészlet az alábbi kimenetet generálta: 3 0 -32384 1 0 0 3 57 979858800 979916248 979746891 Ez így nem teljesen közérthető, ezért az alábbiakban megtekintheti, hogy e számok mit takarnak: 3 a fájlrendszer eszközeinek száma 0 i-csoportmód -32384 fájlmód, típus és engedélyek 1 linkek száma a fájlhoz 0 a fájl tulajdonosának numerikus azonosítója 0 a fájl tulajdonosának numerikus csoport azonosítója 3 eszközazonosító (speciális fájloknál) 57 a fájl mérete (bájtokban) 979858800 utolsó hozzáférési időpont 979916248 utolsó módosítási időpont 979746891 indode változásának ideje Mivel nem jelenik meg minden tulajdonság az összes operációs rendszer alatt, további két dolgot is visszaad a stat (a példa futtatásánál ezek nem jelentek meg), a preferált blokkméretet, valamint a pillanatnyilag
lefoglalt blokkok számát )12. illetve 13 tömbelem lenne) Az időpontok 1970. január 1 0:00 (GMT) óta eltelt másodpercekben vannak megadva, de a dátum- és időkezelésről egy későbbi részben lesz szó. Persze nem kell ennyire belebonyolódni a stat() függvény használatába, amennyiben csak egy-két tulajdonságra van szükségünk, akkor sokkal egyszerűbben is hozzájuthatunk, ha a stat függvényt zárójelbe téve egy számokkal indexelt tömbként kezeljük: $bytes = (stat("adatok.txt"))[7]; print "A fájl mérete $bytes bájt. "; Itt a 7-es számú elemet adtam meg, mert mint az a fenti példa végeredményén látható, a 7. elem a fájl mérete Ha egy másik tulajdonságra van szükség, akkor egyszerűen át kell írni, azonban nem szabad arról megfeledkezni, hogy a sorszámozás 0-tól indul - az első elem a nulladik elem. Könyvtárkezelés Most pedig essen szó a könyvtárakról. Ha csak fájlokat szeretnénk megnyitni, akkor
egyszerűen a fájlnév elé kell tenni a megfelelő útvonalat Mindenhol a "." nevű könyvtár mutat az eggyel fentebb lévőre, a "" az épp aktuálisra, a gyökérkönyvtárba visszalépni a / segítségével lehet Néhány példa: #adatok könyvtárban lévő fájl.txt open(FAJL, "<adatok/fájl.txt"); #a könyvtárstruktúrában az eggyel fentebbi könyvtárban lévő adatok könyvtárban a fájl.txt open(FAJL, "<./adatok/fájltxt"); #az aktuális könyvtárban a fájl.txt open(FAJL, "<./fájltxt"); #a gyökérkönyvtárban a fájl.txt open(FAJL, "</fájl.txt"); #gyökérkönyvtárban adatok és azon belül szovegek mappában a fájl.txt open(FAJL, "</adatok/szovegek/fájl.txt"); Ha írásra szeretnénk megnyitni állományokat, ugyanez a helyzet. Lehetőség van állományok törlésére is, ezt az unlink() függvénnyel végezhetjük el, paraméternek a fájl nevét, ha szükséges, akkor
elérési úttal együtt kell megadni: unlink("adatok.txt"); #az adatoktxt törlése Nem csak fájlokkal dolgozhatunk, hanem könyvtárakkal is. Könyvtár létrehozására az mkdir() függvény szolgál, csak meg kell adnunk a létrehozandó könyvtár nevét, valamint elérési útját, és a beállítandó jogosultságokat. A jogosultságok a Unix rendszereken használt chmod parancsnál megadott paraméternek felelnek meg, azaz meg kell adnunk a tulajdonos, a csoport, illetve mindenki más számára a jogokat, melyek lehetnek olvasás, írás, és futtatás. Ezekkel kell kombinálnunk úgy, hogy a megfelelő számokat egymás után tesszük Az egyes számjegyek jelentése: 1 futtatható (executable) 2 írható (write) 4 olvasható (read) Ezeket kell összeadnunk az egyes jogosultságokhoz, tehát ahhoz, hogy olvasható és írható legyen egy állomány, a 6-os számot kell megadni, melyet úgy számoltunk ki, hogy összeadtuk az íráshoz és az olvasáshoz tartozó
számot [2+4=6]. Az esetek nagy részében az alábbi számokat szoktuk használni: 0 semmilyen jog, azaz nem hozzáférhető 4 csak olvasható állományokhoz (pl. html fájlok) 5 olvasható illetve futtatható állományokhoz (pl. programok) 6 olvasható, írható (olyan fájlokhoz, amelyeket a program módosít) 7 olvasható, futtatható, írható (ezt a jogot csak a tulajdonos szokta megkapni, mivel egy egyszerre futtatható és írható állomány meglehetősen biztonságtalan) A fenti számokat kell egymás után rendezni tulajdonos-csoport-mindenki más sorrendben. Néhány példa: • • • 644: a tulajdonos által írható és olvasható, mindenki más által csak olvasható 755: tulajdonos által írható, olvasható, futtatható, mindenki más által csak futtatható illetve olvasható 666: mindenki számára írható és olvasható A könyvtárakról tudni kell, hogy megnyitásuk a végrehajtással egyenértékű, így míg egy egyszerű, html állománynak 644-es
jogokat adunk, a könyvtáraknak 755-öset, mivel ha 644-eset adnánk annak is, akkor nem lehetne megnyitni. Ha egy könyvtár 777-es értéket kap, akkor szabadon hozhatók benne létre állományok. E kis kitérő után folytassuk a könyvtárak létrehozását, az alábbi példa létrehozza a dokumentumok könyvtárat: mkdir("dokumentumok", 0755); Ha nem a programot tartalmazó könyvtárba szeretnénk, hanem például a gyökérkönyvtárba, akkor a fentebb látott módon járunk el: mkdir("/dokumentumok", 0755); Egy másik példa: mkdir("/dokumentumok/uj", 0755); Ez létrehozza az "uj" nevű könyvtárat a gyökérben található dokumentumok könyvtárban. Azonban egyszerre csak egy könyvtár hozható létre, tehát ha eddig nem létezett a "dokumentumok", akkor a létrehozás nem fog megtörténni. Ebben az esetben az alábbi formát kell használni: mkdir("/dokumentumok", 0755);
mkdir("/dokumentumok/uj", 0755); Tehát egyesével kell a könyvtárbejegyzéseket elkészíteni. Könyvtárak törlése az rmdir() függvénnyel történik: rmdir("dokumentumok"); Azonban itt is figyelni kell arra, hogy a törölni kívánt könyvtárnak teljesen üresnek kell lennie, sem fájl, sem másik könyvtár nem lehet benne, még ha az üres is. mkdir("dokumentumok", 0755); open(FAJL,">dokumentumok/dok.txt"); print FAJL "Ez egy szövegfájl."; close(FAJL); rmdir("dokumentumok") or die "Nem sikerült eltávolítani! "; print "Az eltávolítás sikeres."; A fentiek alapján nem meglepő, hogy a program a "Nem sikerült eltávolítani!" üzenetet fogja adni. Ezzel a végére értünk a hatodik résznek, a hetedikben szó lesz a könyvtárak tartalmának listázásáról, dátum és időkezelésről, közvetlen hozzáférésű állományokról, stb. Minden jót 7. Közvetlen
hozzáférésű állományok és dátumkezelés Ismét itt a Perl sorozatunk, immáron a hetedik résszel. A fájl- és könyvtárkezelés alapjai után (lásd előző rész) könyvtárak teljes tartalmának az elérésével fogunk foglalkozni, majd a közvetlen hozzáférésű állományok használatának bemutatása következik, mellyel rekordorientált adatok tárolhatók. A cikk végén megismerkedhet a dátum illetve időkezeléssel, és az ezzel kapcsolatos gyakran elkövetett hibák elkerülésével. A Perl egy külön függvénnyel rendelkezik, mellyel a fájlokhoz hasonlóan, egy mutató segítségével megnyithatunk egy könyvtárat. Ez az opendir() függvény, melynek paraméterként először a használni kívánt mutatót, majd a megnyitandó könyvtár nevét kell megadni. A párja a closedir(), mely a megnyitott könyvtárat lezárja. A könyvtárban lévő bejegyzéseket - fájlokat és/vagy további könyvtárakat- a readdir()-rel olvasunk be. Állományokhoz
hasonlóan itt is egy while ciklussal nézzük végig egyenként a bejegyzéseket: opendir(KONYVTAR, "konyvtar"); while ($bejegyzes = readdir(KONYVTAR)) { print "$bejegyzes "; } closedir(KONYVTAR); A program kiírja: . . pspbrwse.jbf tavasz1.jpg tavasz2.jpg Munkafüzet1.xls 1.pl 2.pl 3.pl fotok leiras Mint látható, a listában szerepel a "." és a "" könyvtár, a korábbi részben már volt erről szó, az aktuális, illetve a könyvtárstruktúrában az eggyel fentebb lévő könyvtárra hivatkoznak. Itt is lehetőség van egy tömbbe olvasni a bejegyzéseket: opendir(KONYVTAR, "konyvtar"); @bejegyzesek = readdir(KONYVTAR); closedir(KONYVTAR); Ebben az esetben a bejegyzések a @bejegyzesek tömb elemei lesznek. Most pedig kombináljuk eddigi ismereteinket, készítsünk el egy olyan programot, amely kilistázza egy könyvtár teljes tartalmát, a benne lévő könyvtárak tartalmával együtt, és azokban lévő könyvtárak
tartalmával együtt, és így tovább, a könyvtárstruktúra teljes mélységéig. Lássuk a kódot! @dirs = (.); while (@dirs) { $dir = shift(@dirs); opendir(DIR, $dir); @files = readdir(DIR); closedir(DIR); foreach $file (@files) { if ((-d "$dir/$file") and ($file ne ".") and ($file ne "")) { push(@dirs, "$dir/$file"); next; } print "$dir/$file " unless -d $file; } } exit; Addig egyszerű, hogy egy egyelemű tömböt hoztam létre (@dirs), majd egy ciklus segítségével sorra vettem a tartalmát. Ez így az elején értelmetlennek tűnhet, azonban nem lesz az. A ciklusban levágom a tömb első - épp aktuális - elemét, majd beolvasom a könyvtár tartalmát a @files tömbbe, mely elemein egy foreach ciklussal megyek végig. És most jön egy érdekes részlet, ugyanis megvizsgálom a bejegyzést, és ha az könyvtár, valamint neve nem "." és nem is "", hozzácsatolom a @dirs tömbhöz, majd a következő
bejegyzésnél folytatom a program futását (itt még az épp aktuális bejegyzésekről van szó). Ezzel - amennyiben vannak alkönyvtárak - nőtt a @dirs hossza, így a while ciklus miatt a hozzátoldott könyvtár(ak)on is lefut a kód, így fog kiírásra kerülni az összes fájl. A "" és "" könyvtárakat azért nem szabad, hozzátenni a @dirs tömbhöz, mert akkor végtelen ciklusba fulladna a program, hiszen mindig újból végignézné az aktuális, illetve eggyel fentebb lévő könyvtárat. Amennyiben a vizsgált bejegyzés nem felelt meg az elágazásban lévő feltételnek, a fájl az elérési útjával (a program elején megadott könyvtártól számolva) együtt kiíródik, persze csak akkor, ha nem könyvtár, így kerülhetjük el, hogy a "." illetve "" könyvtárak is kiíródjanak. A kimenet ilyen lesz: ./pspbrwsejbf ./tavasz1jpg ./tavasz2jpg ./Munkafüzet1xls ./1pl ./2pl ./3pl ./fotok/foto1jpg ./fotok/foto2jpg
./fotok/foto3jpg ./fotok/foto4jpg ./leiras/leirasoktxt Most pedig beszéljünk a közvetlen hozzáférésű állományokról. Eddig úgy kezeltünk fájlokat, hogy azok tartalmát sorról-sorra beolvastuk Nagyon nagy, több MB-os állományok esetén ez nem túl gyors megoldás, ilyenkor érdemes közvetlen hozzáféréssel dolgozni. Ez azt jelenti, hogy nem kerül az egész fájl beolvasásra (nem is kell, mivel ezt olyan helyen használjuk, amikor nincs szükség az állomány egész tartalmára, csak egy adattömböt szeretnénk kiolvasni), hanem a fájlban egy pozíciót megadva a fájlkezelő oda ugrik, majd beolvassa az adatokat, így a beolvasás nagyon nagy állományok esetén is gyors marad. Ebben az esetben a fájl ún. rekordokra oszlik, tehát mint már említettem, rekordorientált adatokat tudunk így kezelni Az eljárás lényege a következő: minden rekord hossza fix, tehát ugyanannyi karakter. Persze így nem lenne túl használható, hiszen több, mint
valószínű, hogy a tárolni kívánt adatok hossza nem egyezik meg pontosan egymással, ezért erre van egy függvénypár, a pack() és az unpack(), amelyek az ebből adódó problémáinkat orvosolja. Tehát fix hosszú rekordokról van szó A Perl seek() függvénye segítségével tudjuk az állománymutatót pozícionálni a megfelelő rekordra, egy szám segítségével. 0 rekord, 1 rekord, 2 rekord, és így tovább Ekkor a read()-del beolvassuk magát a rekordot, és máris hozzájutottunk a tárolt adatokhoz. Először vizsgáljunk meg néhány függvényt a fentiek közül! A pack függvény több sztringet egyesít fix hosszúságúra. A sztringek hosszát egyenként meg lehet adni. Ha a sztring hosszabb lenne, mint a megadott érték, akkor a végét levágja; ha rövidebb, akkor szóközökkel tölti fel: $nev = Minta Péter; $mail = peter@minta.com; $url = http://www.petermintacom; $adatok = pack("A20 A20 A30", $nev, $mail, $url); print
"|$adatok|"; A program kiírja: |Minta Péter peter@minta.com http://www.petermintacom | Az eredményt azért írattam | jelek közzé, hogy jól látsszon: a sztringek nem érték el a megadott hosszúságokat, ezért szóközökkel feltöltésre kerültek. Most nézzük meg úgy, hogy a 20, 20, illetve 30-as számok helyére kisebbeket írunk! $adatok = pack("A10 A8 A15", $nev, $mail, $url); print "|$adatok|"; A kimenet így: |Minta Pétepeter@mihttp://www.pete| Az unpack függvény épp az ellenkezőt csinálja, a pack()-kel elkészített sztringet visszaalakítja: $adatok = Minta Péter peter@minta.com http://wwwpetermintacom ; ($nev, $mail, $url) = unpack("A20 A20 A30", $adatok); print "Név: $nev E-mail: $mail URL: $url "; A kimenet ebben az esetben: Név: Minta Péter E-mail: peter@minta.com URL: http://www.petermintacom A pack() és unpack() függvény megismerése után nézzünk meg egy példát a közvetlen
hozzáférésű állományok kezelésére. Az alábbi kód az állományba ír: $nev = Minta Péter; $mail = peter@minta.com; $url = http://www.petermintacom; $adatok = pack("A20 A20 A30", $nev, $mail, $url); open(FILE,"+<adatok.dat"); binmode(FILE); seek(FILE, 0*70, 0); print FILE $adatok; close(FILE); Az $adatok változóba olvastam a pack() által előállított sztringet, majd megnyitottam a fájlt. Ekkor bináris módra állítottam a fájlkezelőt, erre nincs minden operációs rendszer alatt szükség, majd a seek segítségével pozícionáltam, és beírtam az adatokat. A seek függvényről tudni kell, hogy három paramétert kell megadnunk használatakor, a fájlkezelőt, majd egy értéket, hogy melyik pozícióba szeretnénk állítani a mutatót. Itt a 0 a fájl eleje, az 1 az első karakter, a 10 a tizedik karakter, stb Azért használtam a 70*0 formát, mert így jól látható, hogy a 70 a rekordok hosszát jelzi, a 0 pedig a rekord számát.
Tehát az 1*70 az első rekordot, a 270 a másodikat, a 3470 a harmincnegyediket jelenti, és így tovább. Egy rekord hosszát megkapjuk, ha összeadjuk a rekordot felépítő sztringek hosszát, ebben az esetben 20+20+30=70 (ezeket az adatokat kellett megadnunk a pack()-nál is). A harmadik paraméter egy szám, mely azt jelzi, hogy a megadott értéket a Perl honnan számolja; lehet 0: a fájl eleje, 1: az éppen aktuális pozíció, vagy 2: a fájl vége. Az olvasás hasonló módon történik: open(FILE,"+>>adatok.dat"); binmode(FILE); seek(FILE, 0*70, 0); read(FILE, $adatok, 70); close(FILE); ($nev, $mail, $url) = unpack("A20 A20 A30", $adatok); print "Név: $nev E-mail: $mail URL: $url "; Ami új, a read() függvény. A neve is mutatja, adatok beolvasására használható, három paraméterre van szükség, a fájlmutatóra, a változó neve, amibe az adatok kerülnek, végül a hosszra (ennyi karaktert fog beolvasni). Végül
beszéljünk a dátum és időkezelésről. A time() függvény segítségével juthatunk hozzá a jelenlegi időhöz, a függvény egy számot ad vissza, ez az 1970. január 1 0:00 [GMT] óta eltelt másodpercek száma (UNIX dátumkezelés alapján) A localtime() függvény arra szolgál, hogy ezt feldolgozhatóbb formára hozza, egy 9 elemű tömböt ad vissza, melynek elemei az alábbiak: 1. 2. 3. 4. 5. 6. 7. 8. 9. másodperc perc óra nap hónap év , 1900 óta eltelt évek száma hét napja év napja nyári időszámítás Amire oda kell figyelni, az a hónap, az év, és a hét napja. A hónap számozása 0-val indul, tehát a 0 a januárt jelenti, az 1 a februárt, és így tovább. Hasonló a helyzet a hét napjaival is, azonban a hét vasárnappal kezdődik, tehát a 0 fogja a vasárnapot jelenteni, az 1 a hétfőt, a 2 a keddet, és így tovább. Az év az 1900 óta eltelt évek számát adja vissza, tehát 2001-ben 101-et (figyelmetlen programozók ezt úgy hozták
megfelelő formára, hogy az elejéhez csatoltak egy 19-est, mely csak 1999-ig volt megfelelő, ezért írt ki 2000-ben néhány weboldal 19100at). Az alábbi példa kiírja a jelenlegi dátumot és időt "szép" formában: ($mp, $perc, $ora, $nap, $honap, $ev, $het napja, $ev napja, $nyari idoszam) = localtime(time); $ev += 1900; my @honapok = ("Január", "Február", "Március", "Április", "Május", "Június", "Július", "Augusztus", "Szeptember", "Október", "November", "December"); my $honap nev = $honapok[$honap]; my @napok = ("Vasárnap","Hétfő","Kedd","Szerda","Csütörtök","Péntek","Szombat"); $napneve = $napok[$het napja]; print "$ev. $honap nev $nap ($napneve), $ora:$perc:$mp"; Mint látható, a hónap és a hét napja esetében kihasználtam, hogy a számozás
0-tól indul -a hét napjánál nem szabad elfelejteni, az első nap a vasárnap! Az évszám megfelelő alakra hozását nem mindenféle hozzácsatolással oldottam meg, hanem egyszerűen növeltem 1900-zal az értékét - ez a legcélravezetőbb. Nem fontos ilyen sok változót létrehozni, használható az alábbi forma is, mely kiírja az épp aktuális évszámot: $ev = (localtime(time))[5] + 1900; print $ev; A következő példa kombinálja az eddig szerzett ismereteket, egy könyvtár fájljai közül kiválasztja azokat, amelyek 1 napnál régebben voltak módosítva. $hatar = time() - 60*6024; opendir(KONYVTAR, "."); while ($bejegyzes = readdir(KONYVTAR)) { unless (-d $bejegyzes) { print "$bejegyzes " if (stat($bejegyzes))[9] < $hatar; } } closedir(KONYVTAR); Az $hatar változó pontosan a 24 órával ez előtti időpontot kapta értékül, mivel a jelenlegi időpontból [ami másodpercekben van megadva] levontam 60*6024-et (ennyi másodperc van egy
nap alatt, mivel 60 másodperc egy perc, 60 perc egy óra, és ezt kell szorozni 24-gyel, hogy egy napot tegyen ki). A fájl utolsó módosításának/létrehozásának időpontjához a múlt részben ismertetett stat() függvénnyel jutottam hozzá. A mellékelt forráskód. Találkozunk a következő, 8. részben 8. Tömbök, referenciák, modulok A Perl programnyelvet tárgyaló sorozatunk a nyolcadik részéhez érkezett. Az előző néhány részben megismerkedhettünk a fájl- és könyvtárkezeléssel, különböző fájlvizsgálatokkal, a dátum és időkezeléssel, stb. Most megnézzük, hogyan hozhatunk létre többdimenziós tömböket, referenciákat, szó lesz a változók érvényességi tartományairól, és a modulokról is. Mit is nevezünk többdimenziós tömbnek? Már eddig is találkoztunk tömbökkel, azonban ezek mind egy dimenziósak voltak, kulcs-érték párokból álltak. A számokkal indexelt tömbök esetén (@), mint azt neve is mutatja, a kulcsok
növekvő számok voltak - 0, 1, 2, 3, -, a hash-ek (%) esetén a számokat sztringek váltották fel, tehát ezek alapján lehetett elérni az értékeket. A többdimenziós tömböket tömbökből álló tömböknek is nevezhetném. Tehát ahhoz, hogy elérjünk egy elemet, két kulcsot is meg kell adni, először, hogy melyik tömbből akarom kiválasztani, másodszor, hogy az elsőnek kiválasztott tömb melyik elemére van szükségem. Mindkét tömb lehet számokkal indexelt, és hash is, így jön össze a négyféle változat, tehát megkülönböztetünk tömbökből álló tömböket, tömbökből álló hash-eket, hash-ekből álló tömböket, és hash-ekből álló hash-eket. Lássunk egy egyszerű példát! %pontok = ( "Anna" => { "fizika" => "84", "matek" => "91", "irodalom" => "57" }, "Vera" => { "fizika" => "39", "matek" =>
"58", "irodalom" => "89" }, "Imre" => { "fizika" => "77", "matek" => "93", "irodalom" => "76" ) ); Itt egy hash-ekből álló hash-t hoztam létre. A hivatkozás egy elemre az alábbi módon történhet: print $pontok{Anna}->{irodalom}; #kiírja, hogy 57 Ebből a -> rész el is hagyható. Ez alapján a fenti tömböt így is létrehozhattam volna: $pontok{Anna}{"fizika"} = "84"; $pontok{Anna}{"matek"} = "91"; $pontok{Anna}{"irodalom"} = "57"; $pontok{Vera}{"fizika"} = "42"; #és így tovább. Ugyanez nevek nélkül: $pontok[0]{"fizika"} = "84"; $pontok[0]{"matek"} = "91"; $pontok[0]{"irodalom"} = "57"; $pontok[1]{"fizika"} = "42"; #és így tovább. Ebben az esetben tehát már nem adtuk meg a tanulók
neveit, csak egy számot, és szögletes zárójeleket használtunk. Tehát ez már egy hash-ekből álló tömb. Most készítsünk el egy kis programot, ami az elsőnek létrehozott tömb alapján kiszámolja egy adott tárgy átlagát! A forrás: $targy = fizika; $tanulok = 0; foreach $nev (keys %pontok) { $tanulok++; $ossz += $pontok{$nev}{$targy}; print "$nev: $pontok{$nev}{$targy} "; } $atlag = int($ossz / $tanulok); print "-- átlag: $atlag"; Megadtuk, hogy melyik tárgyra vagyunk kíváncsiak, majd a tanulók számát 0-ra állítottuk -e változó növelésével fogjuk számolni a tanulók számát listázáskor. Ez után a %pontok tömbön úgy mentünk végig egy foreach ciklus segítségével, mintha nem is lenne többdimenziós, azonban a ciklus belsejében már a megfelelő hivatkozást használtuk. A kimenet: Imre: 77 Vera: 39 Anna: 84 -- átlag: 66 Most pedig beszéljünk a referenciákról, más néven mutatókról. A referencia egy olyan
változó, amely egy másik változóra mutat Kétféle referenciát különböztetünk meg, a puhát - más néven szimbolikus -, és a keményet - valós -. A különbség, hogy a valós egy másik változó tartalmára mutat, tehát egy memóriacímet tárol, ahol az elérhető. Ezzel szemben a szimbolikus csak egy másik változó nevére mutat, amin keresztül elérhető a tartalom -ergo nem magára a tartalomra. Létrehozásuk az alábbi módon történik: $szoveg = "akármi"; $valos ref = $szoveg; #valós referencia $szimb ref = szoveg; #szimbolikus referencia Most írassuk ki az értéküket! print $szimb ref . " "; print $valos ref; A kimenet: szoveg SCALAR(0x1765194) Ez nem a várt eredmény. A szimbolikus referencia esetén a változó értéke a "szoveg" volt, és ez került itt kiírásra is, a másik esetén pedig az érték az a hely volt, ahol a tartalom megtalálható a memóriában - tehát az $szoveg helye. A referenciákat máshogy
kell meghívni: print ${$szimb ref} . " "; print ${$valos ref}; Mely egyenértékű az alábbival, ugyanis a kapcsos zárójelek elhagyhatók: print $$szimb ref . " "; print $$valos ref; A kimenet így már megfelelő: akármi akármi Nem csak skalárokhoz lehet referenciát készíteni, hanem a többi adattípushoz is, tehát tömbökhöz, hash-ekhez, szubrutinokhoz, sőt, még fájlmutatókhoz is! Nézzünk meg egy referenciát, amely egy tömbre hivatkozik! @szamok = (345, 436, 34636, 243, 12); $tomb ref = @szamok; print $$tomb ref[1]; #kiírja, hogy 436 Mint látható, hasonlóan használjuk, mint a skalárra hivatkozó referenciát. Természetesen itt is készíthetünk valós -mint a példában- és szimbolikus referenciát. Most tekintse meg, hogyan járunk el akkor, ha a referenciára nem, mint skalárra -tehát csak egy elemre-, hanem a tömb egészére van szükségünk: @szamok = (345, 436, 34636, 243, 12); $tomb ref = @szamok; foreach $szam (@$tomb
ref) { print $szam . " "; } Mint látható, ekkor a $ jelet le kell cserélni @-ra, tehát ugyanúgy, mintha nem is referenciával dolgoznánk, a különbség csak annyi, hogy a @ után szükséges még egy $ jel a visszahivatkozáshoz, mert ha az nem lenne, akkor valami ilyesmi eredményt kapnánk: @szamok = (345, 436, 34636, 243, 12); $tomb ref = @szamok; print $tomb ref; Ami egy ilyen kimenetet generál: ARRAY(0x17651b8) Ezek után nem hinném, hogy gondot okozhat egy hash referencia elkészítése: %allatok = ( "hal" => "fekete tetra", "madár" => "veréb", "kutya" => "erdélyi kopó" ); $ref = \%allatok; print $$ref{hal} . " "; #kiírja, hogy fekete tetra #kulcs-érték párok listázása: foreach $kulcs (keys %$ref) { print "$kulcs => $$ref{$kulcs} "; } Ugyanígy készítünk referenciát szubrutinokhoz: sub udvozles { print "Hello!"; } $sub ref = &udvozles;
&$sub ref; Most készítsünk szimbolikus szubrutint. Az alábbi példa napszaktól függő köszöntést állít elő: %koszont = ( "este" => "koszont e", "reggel" => "koszont r", "napkozben" => "koszont n" ); $ora = (localtime(time))[2]; if ( ( $ora < 5 ) or ($ora > 18) ) { $kulcs = "este"; } elsif ( $ora < 9 ) { $kulcs = "reggel"; } else { $kulcs = "napkozben"; } &{$koszont{$kulcs}}; exit; sub koszont e { print "Jó estét!"; } sub koszont r { print "Jó reggelt!"; } sub koszont n { print "Jó napot!"; } Tudom, sokkal egyszerűbben is meg lehetett volna oldani, de így legalább bemutatásra került egy példában több lehetőség is. A program elején egy hash-ben tároltuk a szubrutinok neveit, majd a $ora skalárba tettük a jelenlegi időpontból az órák számát. Ettől függően deklaráltuk a $kulcs változó értékét, majd
egy sorban egyszerre választottuk ki a %koszont-ből a szubrutin nevét, és hívtuk azt meg. Kicsit túlbonyolítottnak tűnhet - mert az is -, azonban remekül látható, hogy referenciák segítségével milyen rugalmasan hívhatunk meg szubrutinokat, vagy hivatkozhatunk tömbökre és tömbelemekre, változókra. Mint már említettem, akár fájlmutatókhoz is készíthetünk referenciát. Ekkor a globális adattípust kell használni (*), ha valódi referenciát szeretnénk. Az alábbi program egy előre megadott fájlba ír egy előre megadott mutató használatával: $mutato = *FAJL; $fajl = "adatok.txt"; open($mutato, ">>$fajl"); print $mutato "Adatok írása a(z) $fajl állományba."; close($mutato); A most következő példa pedig a @ARGV tömbből [lásd 5. rész] veszi a fájlkezelőt a kimenethez: $mutato = *{shift @ARGV}; open(FAJL,">>adatok.txt"); print $mutato "A program kimenete. "; close(FAJL); A
program kimenete a meghívásnál megadott első paraméterben meghatározott mutatóra fog íródni. Ha a programot az alábbi módon hívjuk meg, akkor kiírja az üzenetet a képernyőre: perl prgnev.pl STDOUT Ha pedig így, akkor az adatok.txt-be fogja írni: perl prgnev.pl FAJL A változók érvényességi tartományai Most pedig beszéljünk a változók érvényességi tartományairól. Ezt a változó típusa adja meg, mely lehet globális - ez az alapértelmezett -, és lokális/helyi, melyeknek további két fajtája van, a lexikus és a dinamikus. Eddig globális változókkal dolgoztunk, ami azt jelenti, az összes változó elérhető volt a program bármely részéről. Az alábbi program egy előre deklarált változót használ fel egy szubrutinban, majd egy szubrutinban deklaráltat a programban: $globalis = "változó tartalma"; &alprg; print $szubrutinban; #$kiírja, hogy változó tartalma exit; sub alprg { $szubrutinban = $globalis; } Ebből
remekül látszik, hogy a skalár még egy szubrutinból is elérhető volt, és a szubrutinban létrehozott változó a főprogramban is elérhető maradt. Igen kényelmes így használni a változókat, azonban nem mindig szükséges, hiszen nagyon sokra csak ideiglenesen van szükség, a programnak csak egy bizonyos részében, máshol nem. A globális változók ekkor is a memóriában maradnak, tehát foglalják a helyet Ez legtöbbször szubrutinokban, ciklusokban, és elágazásokban fordul elő, ilyenkor érdemes lokális változókat használni. Mint már említettem, kétféle lokális változótípust különböztetünk meg. A dinamikus változó elérhető minden szubrutinból, amit azon a szubrutinon belül hívtunk meg, ahol a változót létrehoztuk. Az ilyen típust a local előtag használatával kell deklarálni: sub alprg { local $szam = 33; &szamkiir; } sub szamkiir { print $szam; } &alprg; #kiírja, hogy 33 &szamkiir; #nem ír ki semmit Mivel az
alprg() szubrutinban lokális változót hoztam létre, az innen meghívott szamkiir() alprogram kiírta a számot, viszont utána, amikor a főprogramból hívtam meg, már nem írta ki, mivel ekkor már nem létezett a változó. Ha az alprg()-ben globális változót hoztam volna létre, akkor a $szam kétszer került volna kiírásra. A dinamikus változóval szemben a lexikus csak ott érhető el, ahol deklaráltuk, tehát az onnan hívott szubrutinban már nem. Ilyen változó a my-jal hozható létre: sub alprg { my $szam = 33; &szamkiir; #nem ír ki semmit print $szam; #kiírja, hogy 33 } sub szamkiir { print $szam; } &alprg; &szamkiir; #nem ír ki semmit Mint látható, itt már a szamkiir() szubrutin egyáltalán nem írt ki semmit, még akkor sem, ha az alprg() szubrutinból hívtuk meg. A kiírás csak az alprg() szubrutinban működött. Perl-ben lehetőségünk van ún. modulok készítésére is Ezekben a gyakran használt szubrutinokat lehet
összegyűjteni, és azokat ezzel több helyről is használni. Az itt használt változók külön táblában tárolódnak, ezért nem fognak "összegabalyodni" a program változóival Nem csak a modul szubrutinjait, hanem a változóit is el lehet érni $modulneve::változó formában. Célszerű az így elkészített modulokat egy külön fájlban tárolni, és azokat a use segítségével meghívni azon programokban, amelyekben szükség van rá. Egy egyszerű példa - itt egy fájlban a programmal együtt: $sugar = 5; $magassag = 8; $terfogat = &szamitasok::henger terfogat($sugar, $magassag); print "V = $sugar ^ 2 * $szamitasok::pi $magassag = $terfogat"; exit; package szamitasok; sub henger terfogat { $pi = 3.1415926535897932; return $ [0]*2 $pi $ [1] ; } Egy szamitasok nevű modult hoztam létre, amiben egy szubrutint készítettem, melyben deklarálásra kerül egy $pi változó, a ? értékének első néhány tizedesjegyével, majd a visszatérési
értéknél néhány műveletet végez el a kapott két paraméterrel. Ennyi lenne a modul, a programban két változót deklaráltam, majd azokat megadva paraméterként a modul visszatérési értékét egy harmadikba olvastam be, végül kiírtam a számítás menetét, és a végeredményt. Jól látható, hogy a modul szubrutinjára való hivatkozás &szamitasok::henger terfogat(paraméterek) formában történt, az $pi változó értékéhez pedig $szamitasok::pi módon jutottam hozzá. Akár több modult is egymásba ágyazhatunk, ekkor $modul1::modul2::valtozo formában történik a hivatkozás. Maga a program mindig a main modulban indul, tehát az egyébként $sugar-nak írt skalárt akár $main::sugar formában is írhatnánk. Ha külső fájlban szeretnénk elhelyezni a modult, akkor a fájl neve legyen a package utasításnál megadott név .pm kiterjesztéssel, a fenti esetben "szamitasok.pm" A fő programban csak az use() utasítást kell kiadni, megadva a
modul nevét, pm kiterjesztés nélkül Ebben az esetben valahogy így: use szamitasok; Itt kell szót ejtenem a @INC tömbről, melyben azok az elérési utak vannak felsorolva, amelyekben a Perl keresi a modulokat, ezért ha nem az alapértelmezett hely(ek)en tároljuk ezeket, akkor meghívás előtt ki kell adni egy push @INC, "/eleresi/ut/a/modulokhoz"; utasítást (hozzácsatoljuk a tömbhöz az új elérési utat). A modulon belül további utasításokat lehet megadni, amelyek a modul betöltődésekor, ill. kitöltődésekor fognak végrehajtódni Ezeket a BEGIN { . } és az END { } -el tehetjük meg: &modul::szubrutin; exit; package modul; BEGIN { print "A modul betöltődött. "; } sub szubrutin { print "A szubrutin lefutott. "; } END { print "A modul kitöltődött. "; } A kimenet: A modul betöltődött. A szubrutin lefutott. A modul kitöltődött. Mint látható, a program futásakor azonnal végrehajtódott a BEGIN
utasításblokk, a futás végén pedig az END. Végül lássunk egy összetettebb példát, mely a bekért adatok alapján egy külső modul segítségével kiszámolja egy henger néhány adatát! A szamitasok.pm tartalma: package szamitasok; BEGIN { $pi = 3.1415926535897932; } sub henger terfogat { local $alapterulet = &kor terulet($ [0]); return $alapterulet * $ [1] ; } sub henger felszin { local $alapterulet = &kor terulet($ [0]); local $alapkerulet = &kor kerulet($ [0]); return 2 * $alapterulet + $alapkerulet $ [1] ; } sub kor terulet { return $ [0]*2 $pi; } sub kor kerulet { return 2 * $ [0] $pi; } 1; A henger.pl tartalma: push @INC, "."; use szamitasok; print "Adja meg egy henger alapjának a sugarát! "; $sugar = <STDIN>; chomp($sugar); print "Adja meg a henger magasságát! "; $magassag = <STDIN>; chomp($magassag); print "-" x 40 . " "; print "A henger alapkerülete: " .
&szamitasok::kor kerulet($sugar) " "; print " alapterülete: " . &szamitasok::kor terulet($sugar) " "; print " felszíne: " . &szamitasok::henger felszin($sugar, $magassag) " "; print " térfogata: " . &szamitasok::henger terfogat($sugar, $magassag) " "; exit; A modulban négy szubrutin található, melyek némelyike egy másikat is használ, valamint van egy BEGIN rész is, amelyben deklarálásra kerül az $pi változó. A programban a modul betöltése után a szükséges adatok kerülnek bekérésre - a henger alapkörének sugara, valamint a test magassága -, majd a modul segítségével az adatok kiszámításra kerülnek, és kiíródnak. A program a modullal együtt megtalálható itt megtalálható. 9. Objektumok és adatbáziskezelés A sorozat előző részében szó volt a többdimenziós tömbökről, majd a referenciákról, a változók érvényességi köréről, és a
modulokról. A mostani rész elején néhány speciális változót ismertetek, majd a DBM adatbázisok használatát mutatom be, ezek után pedig az objektum orientált programozás alapjairól szólok. Tehát akkor nézzünk meg néhány előre definiált Perl változót Az egyik leggyakrabban használt ilyen skalár a $ , mely az alapértelmezett változó. Sok helyen használható, például néhány függvény esetén, ha nem adunk meg paramétert, a Perl ezt fogja használni: while (<STDIN>) { chomp; s/./a/g; print; } Ez a kis program például a beírt adatok végéről levágja a sortörést, majd az összes karakterét egy "a" betűre cseréli le, és kiírja a kapott sztringet. Mindezt anélkül, hogy egyetlen változónév is szerepelne a programban A $/ az input rekordszeparátort határozza meg, más néven azt a karaktert, amely a beviteli adatok elválasztására szolgál. Ez alapesetben az újsor karakter ( ), melyet meg is változtathatunk: $/ =
"vege"; $in = <STDIN>; print " A beírt adatok: --- $in ---"; A kódot lefuttatva a $in változónak akár többsoros szöveg is megadható, a beolvasás mindaddig tartani fog, amíg nem talál "vege" karaktersorozatot a beolvasott sorban. A $/ párja a $, mely a kimeneti rekordszeparátor. Ez fog kiíródni minden rekord után Alapesetben üres $ = "[print vége] "; print "A romantika a klasszicizmusból nőtt ki,"; print "de hamarosan szembefordult vele."; print "Elődje a szentimentalizmus volt."; A kimeneten minden kiírt rekord után megjelenik a $ tartalma: A romantika a klasszicizmusból nőtt ki,[print vége] de hamarosan szembefordult vele.[print vége] Elődje a szentimentalizmus volt.[print vége] Az $1,$2,$3 változókkal már biztosan találkozott, ezek a reguláris kifejezésekben eltárolt sztringek, az eltárolás sorrendjében: $ipcim = "127.001"; $ipcim =~
/(d{1,3}).(d{1,3})(d{1,3})(d{1,3})/; print " Az IP címben szereplő négy szám: $1, $2, $3, $4."; A kimenet: Az IP címben szereplő négy szám: 127, 0, 0, 1. Mintaillesztés esetén használható a $& skalár, amely az utoljára illesztett részt tartalmazza: $valtozo = "piros alma"; $valtozo =~ s/a(.+)a/b1b/; print "A $valtozo-ben eredetileg $& szerepelt."; A kimenet: A piros blmb-ben eredetileg alma szerepelt. A $! skalárban mindig az utolsó hibaüzenet van: open(FAJL,"<nincs-ilyen-fajl.txt") or die "A program ezt a hibaüzenetet adta: $! "; A kimenet abban az esetben, ha nem található a "nincs-ilyen-fajl.txt" fájl: A program ezt a hibaüzenetet adta: No such file or directory A $0 változóban az éppen futó program neve található: print $0; Nálam épp ezt írta ki: D:My DocumentsPROGWORLDPerl9peldaprg.pl A $] ill. $^O változók a használt Perl verziót illetve az operációs rendszer
nevét tartalmazzák: print "Oprendszer: $^O Perl verzió: $]"; A kimenetből jól látszik, hogy ezt most épp Windows alatt írom, és 5.006-os a Perl fordítóm: Oprendszer: MSWin32 Perl verzió: 5.006 A $^T változó a program futásának kezdetét tárolja (1970. január 1 0:00 [GMT] óta eltelt másodpercek száma) A @ARGV tömbbel már találkoztunk egy régebbi részben, a programnak átadott parancssori argumentumok listájáról van szó. A @INC azokat a könyvtárakat tartalmazza, ahol a Perl egy modul után fog keresgélni. Ennek van egy hash párja, a %INC, mely a használt modulokat tartalmazza, a kulcsok az állománynevek, az értékek pedig az elérési utak. A %ENV hash a környezeti változók tömbje, a kulcsok a változók nevei, a hozzá tartozó értékek pedig maguk a beállítások. Most pedig beszéljünk a DBM adatbázisokról. Azért csak erről szólok, mert ez igaz ugyan, hogy elég primitív, viszont igen támogatott, és a fordítón kívül
nincs szükség másra. Az adatokhoz egy hash-en keresztül férhetünk hozzá, ami elméletileg bármiből bármennyit tartalmazhat A használat rendkívül egyszerű, ugyanis a Perl egy külön függvényt tart fenn, amely segítségével az adatbázis tartalmát egy tetszőleges asszociatív tömbhöz rendelhetjük hozzá, ez fog felületet biztosítani az adatbázis kezeléséhez. Minden műveletet ezen a hash-en kell végrehajtani, és ezek a változások mind meg fognak jelenni az adatbázisban is. Mint már említettem, ez a módszer csak igen egyszerű adatbázisok esetén alkalmazható, mivel csak egyszerű kulcs-érték párok tárolására alkalmas. Most nézzük meg, hogyan használható ez a gyakorlatban! dbmopen(%adatok, "adatok", 0666); $adatok{piros} = "szín"; $adatok{monitor} = "hardver"; $adatok{teve} = "állat"; dbmclose(%adatok); A dbmopen() függvény segítségével nyitható meg egy ilyen adatbázis. Három paramétert
kell megadni, a felületül szolgáló hash nevét, az adatbázis nevét -ez lesz a file neve is, .dir és pag kiterjesztéssel-, végül a hozzáférési jogokat A dbmclose() használatával zárható be az adatbázis, ekkor a hash is eltűnik. Most listázzuk ki a létrehozott adatbázisunk tartalmát! dbmopen(%adatok, "adatok", 0666); foreach (keys %adatok) { print "$ : $adatok{$ } "; } dbmclose(%adatok); A kimenet: piros: szín monitor: hardver teve: állat Új elemek hozzáadása, vagy a már meglévők módosítása, törlése a már megismert módon történik -az értékadó operátorral, illetve a delete() függvénnyel. Most pedig beszéljünk egy kicsit az objektum orientált programozásról Perlben. Akiknek már vannak programozási ismereteik, biztosan találkoztak már ezzel a kifejezéssel. Az eljárás előnye, hogy az így írt programok sokkal rugalmasabban fejleszthetők, illetve újrafelhasználhatók, és nem utolsó sorban a kód
strukturáltabb, áttekinthetőbb. Mi is maga az objektum? Egy referencia,azonban nem olyan, mint amikkel eddig találkozott. Az különbözteti meg, hogy létrehozásakor a bless() segítségével "megáldjuk", ami annyit jelent, hogy az is eltárolásra kerül, hogy hol -melyik osztályban - jött létre. Az osztály egy egyszerű modult jelent, és az itt létrehozott függvényeket metódusoknak nevezzük. Ha nem adunk meg osztálynevet, akkor az objektum oda fog tartozni, ahol létrehoztuk Az alábbi példában a $ido referenciát a "Datum" osztály objektumává tesszük: bless $ido, "Datum"; A referencia általában egy hash szokott lenni, mivel ez felel meg a legjobban annak, hogy az objektum összes adata egyszerűen tárolható legyen benne. Az objektum létrehozása egy metódus segítségével történik, melyre az osztályon keresztül hivatkozunk Ezt nevezzük konstruktor metódusnak. Ez után a létrehozott objektumon keresztül érhetjük
el annak metódusait A konstruktor metódus és az objektum metódusai között az a különbség, hogy a konstruktor metódus meghívásánál a paraméterekhez automatikusan hozzácsatolódik az osztály neve, míg az osztály metódusaihoz az objektum referencia, ezzel tudunk majd a metóduson belül további, ugyanahhoz az objektumhoz tartozó metódusokat meghívni. Ezek után hozzunk létre egy igen egyszerű konstruktor metódust: package Datum; sub Uj { my $osztaly = shift; my $ref = {}; bless $ref, $osztaly; return $ref; } Mint mar említettem, a Perl automatikusan kibővíti az átadott argumentumokat az osztály nevével, ezt itt a shift() használatával elkülönítjük az esetleges többi paramétertől, nehogy később zavaró legyen. Ez után létrehoztuk az üres hash referenciát, majd az objektumot És itt jött még egy lényeges sor, a metódus visszatérési értéke, amely az objektumreferencia kell, hogy legyen, mert később ezen keresztül tudjuk majd az
objektum metódusait elérni. A most létrehozott konstruktor metódussal a következőképpen hozhatjuk létre magát az objektumot: $obj = Datum->Uj(); Most hozzunk létre egy metódust, ami visszaadja azt, hogy ma épp hányadika van! sub Hanyadika { my $objref = shift; my $ido = shift; $ido = time unless $ido; return (localtime($ido))[3]; } Mint látható, először kivettem az objektum referenciát, majd az első argumentumot. Ha utóbbi nem létezik, vagy nincs értéke, akkor a time() függvény által visszaadott számot adom neki értékül. A visszatérési érték pedig a localtime() által visszaadott lista negyedik eleme Most pedig hozzunk létre egy Datum.pm nevű fájlt, benne a "package Datum;" sorral, és a két metódussal Az így elkészült objektumorientált modult az alábbi kódrészletben hasznosítjuk: use Datum; $obj = Datum->Uj(); print "Ma " . $obj->Hanyadika() "-a/e van "; print "100 nappal ezel?tt " .
$obj->Hanyadika( time() - 60*6024100 ) . "-a/e volt"; exit; Mint látható, a modul betöltése után a konstruktor metódussal létrehoztam egy objektumot, majd az objektumreferencián ($obj) keresztül értem el az objektum metódusát. Az első esetben nem adtam meg paramétert, mivel mint már tudjuk, az objektum így a jelenlegi időt fogja figyelembe venni, a második esetben pedig a jelenlegi időből vontam ki a 100 év alatt eltelt másodpercek számát (60*6024100). A programot lefuttatva az alábbi kimenet íródott ki: Ma 24-a/e van. 100 nappal ezelőtt 14-a/e volt. A cikk zárásaként egy naplót fogunk elkészíteni, melyben nevekhez tartozó pontszámokat fogunk eltárolni -természetesen egy a cikk elején megismert DBM adatbázis használatával, és objektum orientált modulokkal. Két modult fogunk létrehozni, ez egyikkel lehet majd egy név megadása után az ahhoz tartozó pontszámot kiíratni, a másikkal pedig új névpontszám párt létrehozni,
vagy a már meglévőket módosítani. Először írjuk meg a bejegyzéseket készítő részt A modul létrehozása után készítsük el a konstruktor metódust, a neve legyen mondjuk Uj. package NaploIr; sub Uj { my $type = shift; my $fajl = shift; my $self = {}; bless $self, $type; $self->{"fajl"} = $fajl; return $self; } Ami érdekes lehet, hogy a kapott paramétert eltárolja az objektum referenciában, hogy az később is hozzáférhető legyen. Ez után készítsük el az adatok bekérésére szolgáló metódust, a neve értelemszerűen "Beker" lesz. sub Beker { my $self = shift; my ($nev, $pontszam); NEV: { print "Név: "; $nev = <STDIN>; chomp($nev); print "Hiányos adatok! " and redo unless $nev; } PONTSZAM: { print "Pontszám: "; $pontszam = <STDIN>; chomp($pontszam); print "Hiányos adatok! " and redo unless $pontszam; } return $nev, $pontszam; } A metódus elején definiáltunk két lokális
változót, amelybe később a beírt adatok fognak kerülni. Az adatok bekérése egy külön utasításblokkban történik, melynek a végén a program ellenőrzi a beírtakat, és ha a felhasználó nem adta meg a szükséges adatot, akkor egy hibaüzenet kíséretében a program visszaugrik a blokk elejére (redo). A visszatérési értékek a kapott adatok Már létre tudjuk hozni az objektumot, az adatokat is be tudjuk olvasni, egyetlen metódus van még hátra, mely a kulcs-érték - ebben az esetben név-pontszám párokat eltárolja. Nevezzük el "Beir"-nak sub Beir { my ($self, $nev, $pontszam) = @ ; dbmopen(%naplo, $self->{"fajl"}, 0666); $naplo{$nev} = $pontszam; dbmclose(%adatok); } Most már tényleg mindennel megvagyunk, a modul kész, már csak egy program kell, ami az egészet működtetni fogja. use NaploIr; $naplo = NaploIr->Uj("naplo"); ($nev, $pontszam) = $naplo->Beker(); $naplo->Beir($nev, $pontszam); print "$nev
sikeresen hozzáadva."; exit; A modul betöltése, és az objektum létrehozása után a $nev és $pontszam változóba kértük be az adatokat, melyeket tovább adtunk az eltárolást végző metódusnak. Ezt egy sorban is el lehetett volna intézni: $naplo->Beir( $naplo->Beker() ); Így viszont nem jutottunk volna hozzá a beírt adatokhoz, tehát a $nev változó hiányában nem tudtuk volna a fent látható üzenetet abban a formában kiírni. Most pedig készítsük el a másik modult, amely a pontszámok kiírására szolgál Itt is a konstruktor metódussal kezdünk: package NaploOlvas; sub Uj { my $type = shift; my $self = {}; bless $self, $type; $self->Beolvas(@ ); return $self; } Jól látható, hogy az objektum elkészítése után hogyan hivatkozunk egy másik metódusra. Ebben az esetben azonnal meghívjuk a "Beolvas" metódust, argumentumként a kapott paraméter(eke)t megadva. sub Beolvas { my ($self, $fajl) = @ ; dbmopen(%naplo, $fajl, 0666);
foreach (keys %naplo) { $self->{"naplo"}->{$ } = $naplo{$ }; } dbmclose(%adatok); } Itt történik meg maga a beolvasás, és az objektumreferenciában való eltárolás. Így ha egy programon belül többször is szükségünk van a pontszámokra, nem kell mindig beolvasni azokat, mivel benn vannak a memóriában. A memóriából történő kiolvasásra is készítünk egy metódust: sub Olvas { my ($self, $nev) = @ ; return $self->{"naplo"}->{$nev} if defined $self->{"naplo"}->{$nev}; } Ez egyszerűen visszaadja a kért névhez tartozó pontszámot, ha létezik a megadott kulcs a hash-ben. Már csak a név bekérését, és visszaadását kell megoldani, mely szintén egyszerű: sub NevBeker { my $nev; print "Név: "; $nev = <STDIN>; chomp($nev); return $nev; } Ezzel készen vagyunk a pontok lekérdezéséhez szükséges objektummal is, melyet az alábbi programban használunk fel: use NaploOlvas; $naplo =
NaploOlvas->Uj("naplo"); $nev = $naplo->NevBeker(); if ( $naplo->Olvas($nev) ) { print "$nev pontszáma: " . $naplo->Olvas($nev); } else { print "Nem találtam $nev nev? bejegyzést."; } exit; Az objektum létrehozása, és a keresett név bekérése után következik a program lényegi része: ha a névhez tartozik pontszám - melyet az Olvas() metódussal kapunk meg -, akkor kiírjuk, ha nem, akkor egy hibaüzenetet jelenítünk meg. A cikkben szereplő példaprogramok megtalálhatók a cikk mellett. A következő részekben a Perl CGI programokban történő alkalmazásáról lesz szó, addig is minden jót. 10. CGI scriptek 1 Perl sorozatunk eddigi részeiben magáról a programnyelvről beszéltünk, a mostani, tizedik részben a Perl fő felhasználási területéről, a CGI programokról lesz szó. Biztosan ismerősen cseng ez a név azoknak, akik már jártasak valamilyen szinten a honlapok készítésében A CGI (Common Gateway
Interface, általános átjáró felület) nevében is benne van, hogy - mindenféle tévhittel ellentétben - nem egy programnyelvről van szó, hanem egy felületről, amely összeköti programunkat a HTTP szerverrel, ezen keresztül a felhasználóval. Belátható, hogy CGI programok írásához akármelyik programnyelv felhasználható, egyetlen megkötés, hogy az adott operációs rendszer támogassa azt, és emellett célszerű viszonylag gyorsnak lennie. Az így elkészített alkalmazásaink a WWW-n -World Wide Web- keresztül válnak elérhetővé, így gyakorlatilag bárhonnan használhatók, ahol hozzáférhető a Világháló. A technika lényege a dinamikus adatok megjelenítése, tárolása. Fontos róla tudni, hogy az ilyen programok a szerver oldalon hajtódnak végre, tehát nem vetődhet fel olyan probléma, hogy a kliens program -a felhasználó böngészője- nem támogatja ezeket. A honlapokba való integrálásra többféle mód is van, a leggyakrabban csak egy
egyszerű hivatkozást teszünk rá, például egy link elhelyezésével -ugyanúgy, mintha az egy HTML dokumentum lenne-, de szintén mindennapos az űrlapok feldolgozásánál való alkalmazás. Ezekben az esetekben a programunk feladata az adatok feldolgozása, és/vagy tárolása, valamint továbbítása mellett egy megfelelő HTML lap előállítása lesz. Használhatunk SSI (Server Side Includes) hivatkozásokat, melyekről egy későbbi részben lesz szó Tudni kell azonban, hogy a CGI felület alkalmas bármilyen formátumú kimenet előállítására, így akár grafikus kimenetet is készíthetünk, tehát ilyen programokat képként is beilleszthetünk HTML lapokba. Ebből kikövetkeztethető, hogy a kimenet generálásakor nem csak magát a tartalmat, hanem egy válasz-fejlécet is el kell készíteni. Ez általában egy "Content-type: [mime típus]" sorból áll, mellyel meghatározhatjuk a kimenet tartalmának típusát (MIME, Multipurpose Internet Mail
Extension). Egy másik hasonló fejléc a Location, mely egy másik fájlra, vagy URL-re irányítja át a szörfprogramot. Fontos még tudni, hogy a fejlécet el kell választani a többi adattól, ezt két újsor karakterrel tehetjük meg ( ) A típus megadása egy fő, és egy altípussal történik. Néhány gyakran használtak közül: text/html HTML tartalom (.htm, html) text/plain egyszerű szöveg (.txt) image/jpeg JPEG grafika (.jpg, jpeg, jpe) image/gif GIF kép (.gif) video/mpeg MPEG videó (.mpeg, mpg, mpe) video/quicktime QuickTime videó (.mov, qt) application/zip ZIP archív (.zip) application/octet-stream az ilyen tartalmat a böngésző nem jeleníti meg, hanem felkínálja lementésre Az esetek nagy részében a programot tartalmazó fájl első sorában meg kell adnunk a szervernek a fordító elérési útját egy "#!/elérési/út/fordító" sorral, mely Unix rendszereknél általában /usr/bin/perl, vagy /usr/local/bin/perl - az
elérési útról a szerver webmesterétől kaphatunk felvilágosítást. Még egy fontos dolog, mely gyakran szokott hibát okozni, a sorelválasztások típusa. Ha egy Windows rendszer alatt megírt programot feltöltünk egy Unix szerverre, akkor az általában az 500 Internal Server Error hibaüzenetet adja. A hiba oka egyszerű Tekintsünk meg egy több sort tartalmazó Unix, majd egy hasonló, Windows alatt készült fájlt hexa módban egy erre alkalmas programmal (pl. a Windows Commander nézőkéje), és vizsgáljuk meg a sorok között lévő karaktereket. A Windows alatt készült fájlban két karakter is van a sorok között, melyek kódja 0D, illetve 0A. Ezek átszámolva a 13, és a 10-es számú karakterek, tehát a "kocsivissza" - -, és a "sortörés" - - Ezzel szemben a Unix-os formátumú fájl esetén csak egy 0A, azaz egy sortörés karakter van. Ha megnézünk egy Mac formátumút, akkor azt tapasztaljuk, hogy ott pedig csak egy kocsivissza
karakter zárja a sorokat. Azt hiszem, mondanom sem kell, hogy a sorok lezárása mindig olyan formátumú kell, hogy legyen, amilyen operációs rendszer fut a szerver gépen. A fejlettebb szövegszerkesztőkben már van lehetőség a fájlok más formátumban való elmentésére, de ha nem lenne, akkor remekül használható a cikk mellett lévő "dos2unix.com" program, melyet parancssorból használva, paraméterként az átírásra szánt fájlokat megadva könnyen megoldhatjuk ezt a problémát. Ha már Unix rendszereknél tartunk, akkor a jogosultságokat is meg kell említenem, melyekről már egy előbbi számban olvashattunk. A programokat futtathatóvá kell tenni, és mivel nem előre lefordított programról van szó, az olvasási jog is szükséges, tehát a "chmod 755 programnév.cgi" utasítást kell kiadnunk -így nekünk az olvasás, és a végrehajtás mellett írási jogunk is lesz A legfőbb tudnivalók most már a birtokunkban vannak,
készítsünk el egy igen egyszerű CGI programot! #!/usr/bin/perl print "Content-type: text/html "; print "Helló, Világ. "; exit; Most pedig futtassuk le egy webszerveren keresztül! Persze ez nem egy szabályos HTML oldal, ezért bővítsük tovább programunkat, készítsünk dinamikus kimenetet! #!/usr/bin/perl $hanyadika = (localtime(time))[3]; print "Content-type: text/html "; print <<VEGE; <HTML> <HEAD> <TITLE>CGI példa</TITLE> </HEAD> <BODY BGCOLOR="#CCCCCC" TEXT="#666666"> <H1>CGI példa</H1> <HR NOSHADE> <H3>"Helló, Világ!"</H3> <P><FONT COLOR="#000099">Ma <B>$hanyadika</B>-e/a van.</FONT> </BODY> </HTML> VEGE exit; A kimenet így már szabályos HTML kód, és dinamikus is, hiszen minden nap más számot ír ki: Az előző részben említettem egy %ENV nevű hash-t, melyben a
környezeti változók tárolódnak. CGI programoknál ezeket nagyon gyakran használjuk, hiszen rengeteg információ kinyerhető belőlük, és szinte az összes paraméter ezeken keresztül kerül átadásra. Hogy mi is szerepel ebben a tömbben? Írjunk egy programot, ami kilistázza! #!/usr/bin/perl print "Content-type: text/html "; print <<VEGE; <HTML> <HEAD> <TITLE>CGI környezeti változók</TITLE> </HEAD> <BODY> <H1>CGI környezeti változók</H1> <PRE> VEGE foreach (keys %ENV) { print "<B>$ :</B> $ENV{$ } "; } print "</PRE> </BODY> </HTML>"; exit; A kimenet: Vegyük sorra a fontosabb kulcs-értél párokat. SERVER SOFTWARE A szerver-program neve, és verziója. GATEWAY INTERFACE A gateway (a webszerver és a program közötti felület) neve és verziója, általában "CGI/1.1" DOCUMENT ROOT Az a könyvtár, amit a kliens a
gyökérkönyvtárnak lát. REMOTE ADDR A kliens, vagy az általa használt proxy szerver IP címe. REMOTE HOST A kliens host neve, általában nem áll rendelkezésre (a szerver nem határozza meg, vagy egyáltalán nincs). SERVER PROTOCOL A használt protokoll, és annak verziója. REQUEST METHOD A HTTP kérés típusa (ld. később) QUERY STRING GET típusú hívás esetén ebből nyerhetők ki az adatok. CONTENT LENGTH POST típusú hívás esetén az elküldött bájtok száma (ld. később) HTTP USER AGENT A kliens böngészőprogramjának a neve és verziója, általában az operációs rendszerrel kiegészítve. HTTP ACCEPT A kliens által elfogadott mime típusok listája, általában szerepel benne a */, mivel így a szerver minden típust elküld, és a böngésző dönti el, hogy mit kezd vele. HTTP ACCEPT LANGUAGE Azokat a nyelveket találjuk itt, amelyeket a böngésző tulajdonosa beállított, hogy elfogad, el tud olvasni. SCRIPT NAME Programunk
virtuális helye (azért virtuális, mert itt az a könyvtár számít a gyökérkönyvtárnak, amelyet a kliens is annak lát). SCRIPT FILENAME Programunk helye a szerveren (a valódi gyökérkönyvtárból számolva) SERVER NAME A szerver neve, vagy IP címe. REQUEST URI Ezt az URI-t kérte a kliens. SERVER PORT A port száma, ahol a kérés érkezett. HTTP HOST A szerver host neve. PATH INFO A CGI program virtuális könyvtárként is használható, ebben a változóban a programnév utáni, további alkönyvtárak találhatók. PATH TRANSLATED Az előző teljes változata. CONTENT TYPE A kéréshez csatolt információ mime típusa (ld. később) A SCRIPT NAME, és a SCRIPT FILENAME alapján megkülönböztetünk kétféle elérési utat, egy valódit, és egy virtuálisat. Tudni kell, hogy HTTP-n keresztül nem látható a szerver háttértárának a teljes tartalma. Amit a kliens gyökérkönyvtárnak lát, az nem a szerver gyökérkönyvtára, csak egy, a
szerver program konfigurálásánál megadott mappa. Az alábbi program a környezeti változókat felhasználva meghatározza valódi, és virtuális helyét a szerveren (utóbbi elé a szerver host-ját is kiírja, így kapjuk meg a teljes url-t): #!/usr/bin/perl print "Content-type: text/html "; print <<VEGE; Én <B>http://$ENV{"HTTP HOST"}$ENV{"SCRIPT NAME"}</B> vagyok, de valójában a(z) <B>$ENV{"SCRIPT FILENAME"}</B> elérési úton vagyok megtalálható. VEGE exit; A végeredményről leolvasható, hogy a látszólag /cgi-bin/pelda.cgi fájl valójában hol is található a szerveren: Most pedig készítsünk el egy olyan programot, amely egy virtuális könyvtárként szerepel, melynek látszólagos tartalma egy másik könyvtár bejegyzései lesznek, és közben programunk egy külön fájlba mindig "feljegyzi", hogy melyik dokumentumot, és mikor kérték le. Az egyszerűség kedvéért
virtuális mappánk csak egyszerű, szöveges állományokat tartalmaz, így a mime típus mindig ugyanaz. #!/usr/bin/perl $valodi = "./fajlok" $ENV{"PATH INFO"}; print "Content-type: text/plain "; open(FAJL,"<$valodi") or die print "A fájl nem található."; while (<FAJL>) { print; } close FAJL; open(FAJL,">>letoltesek.txt"); print FAJL &datum . " " $ENV{"PATH INFO"} " "; close FAJL; exit; sub datum { my ($mp, $perc, $ora, $nap, $honap, $ev) = localtime(time); $ev += 1900; $honap++; return "$ev-$honap-$nap $ora:$perc:$mp"; } A program feladata egyszerű, először is a PATH INFO környezeti változóból, és a valódi könyvtárból összerakja a kért fájl valódi elérési útját, majd a válasz-fejléc után kiírja annak tartalmát, amennyiben a megnyitás sikeres. Ezzel a megjelenítő rész lezárult, most már csak a feljegyzés elkészítése van
hátra, melyet a program a "letotlesek.txt" nevű fájlba fog írni A &datum nevű szubrutin az aktuális dátumot, és időpontot állítja elő, majd egy tabulátor után a kért fájl neve kerül feljegyzésre. A futtatásnál akár alkönyvtárakban lévő állományokat is megadhatunk, melyek tartalma kiírásra kerül, tehát programunk úgy viselkedik, mintha valóban egy mappa lenne, egy másik mappa tartalmával. Mint már említettem, a bemeneti adatok nagy részét a CGI programok a környezeti változókon keresztül kapják meg a böngészőtől. Két lehetséges módon küldhetünk adatokat, ezek a GET, és a POST eljárások. A GET esetén a sztring az url-ben kerül elküldésre, melyet egy kérdőjel választ el a program nevétől, például: http://www.szerverhu/cgi-bin/programcgi?adatok A QUERY STRING változó tartalmazza az így elküldött sztringet. A POST eljárás ebből a szempontból kivételnek számít, mivel ebben az esetben a küldött
karaktereket nem egy környezeti változóból, hanem a standard bemenetről (STDIN) olvashatjuk be. Azonban tudni kell, hogy a sztring után nincs fájlvége karakter, ezért az így elküldött adatok beolvasásánál szükségünk van az elküldött karakterek számára. Erre szolgál a CONTENT LENGTH környezeti változó, mely ezt a számot tartalmazza. Ezek után belátható, hogy hogyan olvashatjuk be a POST metódussal küldött adatokat egy változóba: read(STDIN, $adatok, $ENV{"CONTENT LENGTH"}); Az, hogy GET, vagy POST metódust használtunk-e, megtudható a REQUEST METHOD változóból, így a program akár maga is el tudja dönteni, hogy honnan kell beolvasnia az adatokat. Most pedig nézzünk meg egy programot, mely egy HTML űrlapot állít elő, majd a visszaküldés után megjeleníti a kapott adatokat. #!/usr/bin/perl read STDIN, $buffer, $ENV{"CONTENT LENGTH"}; print <<END; Content-type: text/html <HTML> <HEAD> <TITLE>CGI
példa</TITLE> </HEAD> <BODY BGCOLOR="#EEEEEE" TEXT="#006699"> <H3>?rlap</H3> <FORM ACTION="$ENV{"SCRIPT NAME"}" METHOD="post"> Név: <INPUT TYPE="text" NAME="nev"> <BR>Életkor: <SELECT NAME="kor"> <OPTION>Kérem, válasszon. <OPTION VALUE="-14">14 év alatt <OPTION VALUE="15-24">15-24 év <OPTION VALUE="25-39">25-39 év <OPTION VALUE="40-">40 évnél több</SELECT> <BR><INPUT TYPE="checkbox" NAME="mobil" VALUE="igen"> Van mobiltelefonom. <INPUT TYPE="checkbox" NAME="auto" VALUE="igen"> Van autóm. <BR><INPUT TYPE="submit" VALUE="Mehet!"></FORM> <H3>Url-ben kapott adatok:</H3> <PRE> $ENV{"QUERY STRING"} </PRE> <H3>Standard
bemenetre kapott adatok:</H3> <PRE> $buffer </PRE> </BODY> </HTML> END exit; Az űrlap kitöltése: Majd az elküldés után: Ha a <FORM> elem METHOD paraméterének a GET értéket adjuk, vagy nem adunk meg semmit (mivel a GET az alapértelmezett), akkor az URL így nézett volna ki: "http://localhost/cgi-bin/form.cgi?nev=Kiss+Istv%E1n&kor=15-24&mobil=igen", és a "?" utáni rész az "Url-ben kapott adatok" felirat után lett volna. Most nézzünk meg egy érdekes dolgot, mégpedig a két eljárás kombinációját! Ehhez az űrlap <FORM> elemét az alábbira módosítjuk: <FORM ACTION="$ENV{"SCRIPT NAME"}?extra=adatok" METHOD="post"> Ennek hatására a kitöltött űrlap elküldése után az alábbi végeredményt kapjuk: A METHOD="post" paraméter miatt a CGI a standard bemenetre megkapta a kitöltött adatokat, viszont az ACTION paraméternél a
program neve után -melyet egy környezeti változóból nyertünk ki- további karaktereket írtunk be, melyek így a QUERY STRING változóba kerültek. Mint látható, az átadott adatokat a böngésző valamilyen kódolással látta el, erről a következő részben lesz szó, addig is minden jót! 11. CGI scriptek 2 Az előző részt a bemeneti adatok tárgyalásával zártuk Egy példán keresztül bemutatásra került a GET, és a POST módon elküldött űrlap adatainak kinyerése, melyeket a böngésző valamilyen kódolással látott el. A megjelenített, majd kitöltött űrlapból programunk egy ilyen sztringet írt ki: nev=Kiss+Istv%E1n&kor=15-24&mobil=igen Mint látható, az űrlapnak megfelelő név-érték párokról van szó, melyek egy "&"-el vannak elválasztva egymástól, és néhány karaktert valamiféle kóddal helyettesít. Írjuk át programunkat úgy, hogy a kapott sztringből nyerje ki a név-érték párokat, ez egyszerű feladat
#!/usr/bin/perl print <<END; Content-type: text/html <HTML> <HEAD> <TITLE>CGI példa</TITLE> </HEAD> <BODY BGCOLOR="#EEEEEE" TEXT="#006699"> <H3>Űrlap</H3> <FORM ACTION="$ENV{"SCRIPT NAME"}" METHOD="post"> Név: <INPUT TYPE="text" NAME="nev"> <BR>Életkor: <SELECT NAME="kor"> <OPTION>Kérem, válasszon. <OPTION VALUE="-14">14 év alatt <OPTION VALUE="15-24">15-24 év <OPTION VALUE="25-39">25-39 év <OPTION VALUE="40-">40 évnél több</SELECT> <BR><INPUT TYPE="checkbox" NAME="mobil" VALUE="igen"> Van mobiltelefonom. <INPUT TYPE="checkbox" NAME="auto" VALUE="igen"> Van autóm. <BR><INPUT TYPE="submit" VALUE="Mehet!"></FORM>
<H3>Kapott adatok:</H3> <PRE> END read STDIN, $buffer, $ENV{"CONTENT LENGTH"}; foreach (split /&/, $buffer) { my ($nev, $ertek) = split /=/; print "$nev: $ertek "; } print <<END; </PRE> </BODY> </HTML> END exit; Mint látható, egy foreach() ciklussal oldottam meg. A split() függvény használatával, az "&" jelek mentén összedarabolt sztringből kapott kérdőív-elemeket nem olvastam be külön tömbbe, hanem az egész függvényt tömbként adtam meg. Az így kapott elemekből ismét egy split() utasítás segítségével nyertem ki a név-érték párokat. A kitöltött űrlap így néz ki: Elküldés után pedig: Ezzel külön választottuk az adatokat, azonban még mindig vannak olyan karakterek, amelyek nem megfelelően kerültek megjelenítésre. Ez a kódolás miatt van, mely általában application/x-www-form-urlencoded -ez az URL kódolás típusa, a FORM elemben ez megváltoztatható, az
ENCTYPE paraméterrel. A lényege, hogy a speciális karaktereket egy "%"-el, majd a karakter hexadecimális (16-os számrendszerbeli) kódjával helyettesíti. Az "á" betű kódja a 225, mely hexadecimális alakban E1, így jön ki a "%E1" karaktersorozat E forma alól kivétel a szóköz karakter (" "), mely egy + jellel kerül behelyettesítésre. A Perl-ben a chr() függvény szolgál arra, hogy egy karaktert a száma alapján megkapjunk, a hex() pedig egy hexadecimális számot ír vissza tízes számrendszerbe. Ezek után könnyen megírható a kikódoló rész: $ertek =~ s/+/ /g; $ertek =~ s/%([da-fA-F]{2})/chr(hex($1))/eg; Talán nem teljesen tiszta a mintaillesztés végén az e karakter: azért van, mert így függvényeket is használhatunk a helyettesítőnél - ebben az esetben a chr()-t, és a bele ágyazott hex()-et. A második, bonyolultabb kifejezés egy % jelet keres, majd 2 db karaktert, melyek vagy számok (d, de írhattam
volna a "0-9"-et is), vagy pedig A, B, C, D, E, F, illetve a, b, c, d, e, f karakterek - nem szükséges kiírni a listát, az a-f és az A-F a fenti felsorolással egyenértékű. A % jel utáni részt zárójelek segítségével eltároljuk, majd a hex() használatával tízes számrendszerbe írjuk át, és a kapott számot adjuk a chr() függvénynek paraméterként, hogy abból az előállítsa a kívánt karaktert, ez fog behelyettesítésre kerülni. Űrlapok feldolgozásánál gyakran használjuk még a length() függvényt, mely egy sztring hosszát adja vissza bájtokban. Készítsünk el egy kis programot, amely egy azonosítót kér be, az esetleges speciális karaktereket visszakódolja, és megvizsgálja a kapott sztring hosszát! #!/usr/bin/perl print <<END; Content-type: text/html <HTML> <HEAD> <TITLE>CGI példa</TITLE> </HEAD> <BODY BGCOLOR="#EEEEEE" TEXT="#006699"> <H3>Űrlap</H3>
<FORM ACTION="$ENV{"SCRIPT NAME"}" METHOD="post"> Azonosító: <INPUT TYPE="text" NAME="azonosito"> (max. 7 karakter)<BR> <INPUT TYPE="submit" VALUE="Mehet!"></FORM> <B> END read STDIN, $buffer, $ENV{"CONTENT LENGTH"}; foreach (split /&/, $buffer) { my ($nev, $ertek) = split /=/; $ertek =~ s/+/ /g; $ertek =~ s/%([da-fA-F]{2})/chr(hex($1))/eg; $qs{$nev} = $ertek; } if ($qs{"azonosito"}) { if (length($qs{"azonosito"}) > 7) { print "Túl hosszú adatok!"; } else { print "Az Ön által megadott azonosító: $qs{azonosito}"; } } print <<END; </B> </BODY> </HTML> END exit; Mint látható, az adatok kinyerése hasonló módon történik, ám most a speciális karakterek visszaalakítása után egy hash-ben tároltuk a névérték párokat. Azért ajánlott ezt az eljárást követni, mert így programunk
bármely részén könnyen hozzáférhetünk bármely form-elemhez A következő rész elágazásokból áll. Ha adott meg a felhasználó azonosítót, akkor a hossza kerül megvizsgálásra, és ha az több, mint 7 karakter, akkor hibaüzenetet ad a program, ellenkező esetben kiírja a megadott karaktersorozatot. Most pedig készítsünk el egy vendégkönyvet. Egy egyszerű űrlap feldolgozásáról van szó, majd az adatok tárolásáról, illetve megjelenítéséről #!/usr/bin/perl $data = "vendegk.dat"; $win = 1 if $^O =~ /Win/; A program elején két változót definiálok, az egyik a fájl neve lesz, amiben a bejegyzéseket fogjuk tárolni, a másik esetében pedig programunk a $^O alapján megpróbálja eldönteni a gépen futó operációs rendszer típusát. Ha a változóban szerepel a "Win" karaktersorozat (tehát feltehetően valamilyen Windows rendszer fut a gépen), akkor a $win változó értékét 1-re állítja, ennek majd később lesz
jelentősége. Ha ez a módszer esetleg nem működne megfelelően, akkor manuálisan is beállíthatjuk a változó értékét. read STDIN, $buffer, $ENV{"CONTENT LENGTH"}; foreach (split /&/, $buffer) { my ($nev, $ertek) = split /=/; $ertek =~ s/+/ /g; $ertek =~ s/%([da-fA-F]{2})/chr(hex($1))/eg; $qs{$nev} = $ertek; } print <<END; Content-type: text/html <HTML> <HEAD> <TITLE>Vendégkönyv</TITLE> </HEAD> <BODY BGCOLOR="#EEEEEE" TEXT="#006699"> <H3>Új bejegyzés</H3> END Ez a részlet az eddigiek fényében egyszerűnek mondható, hiszen a már fentebb is látott módon a %qs hash-be olvassuk be a kapott adatokból a név-érték párokat, majd a megjelenített oldal generálása kezdődik el. if ( ($qs{"nev"}) or ($qs{"email"}) or ($qs{"uzenet"}) ) { unless ( ($qs{"nev"}) and ($qs{"email"}) and ($qs{"uzenet"}) ) { print
"<B>Hiányos adatok!</B> "; $nev = $qs{"nev"}; $email = $qs{"email"}; $uzenet = $qs{"uzenet"}; } elsif ( (length($qs{"nev"}) > 40) or (length($qs{"email"}) > 40) or (length($qs{"uzenet"}) > 2000) ) { print "<B>Túl hosszú adatok! A név és az e-mail maximum 40, az üzenet pedig max. 2000 karakter lehet.</B> "; $nev = $qs{"nev"}; $email = $qs{"email"}; $uzenet = $qs{"uzenet"}; } elsif ( $qs{"email"} !~ /.@{2,}{2,}/ ) { print "<B>Hibás e-mail cím!</B> "; $nev = $qs{"nev"}; $email = $qs{"email"}; $uzenet = $qs{"uzenet"}; } else { Ez a rész ellenőrzi a beérkezett adatokat. Az első elágazás azt vizsgálja meg, hogy a három űrlap mezőből (név, e-mail, üzenet - ezek a mezők később kerülnek megjelenítésre) szerepel-e valamelyik. Ha igen, akkor fut le a
tényleges ellenőrzés Ha a három mező valamelyike nem került kitöltésre, akkor a program hibaüzenetet ad, és beállítja a $nev , $email , és $uzenet skalárok értékét -később lesz jelentősége. Ugyanez történik, ha valamelyik mező hossza irreálisan nagy. Ez után a megadott e-mail-cím kerül ellenőrzése, és ha nem felel meg a megadott mintának, szintén hibaüzenetet ad a program. A mintaillesztés azt várja el, hogy a vizsgált adat bármilyen, legalább egy karakterből álló sztringgel kezdődjön, melyet egy @ jel után egy legalább két karakterből álló karaktersorozat, majd egy pont, és ismét egy legalább két bájt hosszúságú sztring kövessen. Ezzel természetesen nem szűrhetők ki a valótlan e-mail címek, azonban a nem e-mail-cím formájú karaktersorozatok igen. Ha a fenti ellenőrzéseken átmentek az adatok, akkor az else ág után kezdődhet a fájlba történő beírás $datum = &datum(); $qs{"nev"} =
&text2html($qs{"nev"}); $qs{"email"} = &text2html($qs{"email"}); $qs{"uzenet"} = &text2html($qs{"uzenet"}); open(FAJL,"+<$data") or die print "Hiba - Nem nyitható meg: $data"; unless ($win) { flock(FAJL,2); } @bejegyz = <FAJL>; seek FAJL, 0, 0; print FAJL join (<|>, $qs{"nev"}, $datum, $qs{"email"}, $qs{"uzenet"}) . " "; foreach (@bejegyz) { print FAJL; } unless ($win) { flock(FAJL,8); } close(FAJL); } } Mint látható, a $datum változó megkapja a datum() szubrutin által visszaadott értéket (ld. később), majd a három mező egy tex2html nevű szubrutinon megy át - ld. szintén később Ez után a fájl (melyet a program elején adtunk meg) megnyitásra kerül írásra, és olvasásra egyaránt. Itt kerül a $win változó felhasználásra, mint látható, a megnyitott fájlon valamilyen műveletet hajtunk végre abban az esetben, ha a
programot futtató gépen nem Windows rendszer található - ergo a $win változónak nincs értéke. De mi is lehet ez a flock() függvény? Mivel programunkat az Internetre szánjuk, elképzelhető, hogy az igen rövid futási idő ellenére pontosan egyszerre hívják majd meg két helyről, mely nem is lenne gond, azonban az írni kívánt fájlból csak egy van, így a két program elképzelhető, hogy éppen egyszerre akarja majd használni a fájlt. Ez a probléma Windows rendszer alatt nem jelent gondot, azonban Unix rendszer esetén használnunk kell az ún Flock programot, melyet a flock() függvénnyel hívhatunk meg. Ez két részből áll, egyszer meg kell hívni azonnal, a fájl írásra való megnyitása után, ekkor a 2-es paraméterrel, majd közvetlenül a bezárás előtt, ekkor a 8-as paraméterrel. A flock program a két hívás között az összes fájlra vonatkozó kérést várakoztatni fogja, így nem fordulhat az elő, hogy az azonos időpontban történő
írás és olvasás esetén az olvasás sikertelen lesz -hiszen szerencsétlen esetben akár törlődhetnének vendégkönyvünk eddigi bejegyzései is. A bejegyzések tárolása külön-külön sorban történik, tehát minden sor 1-1 bejegyzés. Ekkor felvetődik a kérdés, hogy mi történik akkor, ha a bejegyzésben sortörés van. Itt jön a képbe a text2html nevű szubrutin, mely a sortöréseket <BR> -el fogja helyettesíteni, így az egyes mezőkben nem lesz sortörés, a HTML lapon viszont meg fognak jelenni a <BR>-el helyettesített sorelválasztások. A <, >, és az & karaktereket is a megfelelő HTML kóddal cseréli fel, így a felhasználónak nem nyílik arra lehetősége, hogy valamilyen kódot használjon a vendégkönyvünkben - ugyanis a forráskód fog megjelenni. Az egyes adatok elválasztása egy <|> karaktersorozattal fog történni, mivel ilyen karaktersorozat biztos, hogy nem szerepel a bejegyzésekben, ugyanis a < és >
karakterek HTML kóddal kerültek helyettesítésre. Programunk a bejegyzéseket beolvassa egy tömbbe, majd a fájl elejére pozícionálja a mutatót, és az új bejegyzés beírása után - a join() függvénnyel választjuk szét a megadott karaktersorozattal a többi paramétert - egy ciklus segítségével visszaírja alá az eredeti bejegyzéseket. Ezzel az új bejegyzés mindig a fájl elejére fog kerülni Ez után már csak a lap többi részének, és az eddigi bejegyzéseknek a kiírása van hátra. print <<END; <FORM ACTION="$ENV{"SCRIPT NAME"}" METHOD="post"> Név: <INPUT TYPE="text" NAME="nev" VALUE="$nev " SIZE=20 MAXLENGTH=40><BR> E-mail: <INPUT TYPE="text" NAME="email" VALUE="$email " SIZE=20 MAXLENGTH=40><BR> Üzenet: <TEXTAREA COLS=20 ROWS=4 NAME="uzenet">$uzenet </TEXTAREA><BR> <INPUT TYPE="submit"
VALUE="Mehet!"></FORM> <H3>Eddigi bejegyzések</H3> END open(FAJL,"<$data") or die print "Hiba - Nem nyitható meg: $data"; while (<FAJL>) { chomp(); my ($nev, $datum, $email, $uzenet) = split /<|>/; print "<B>Név:</B> <I>$nev</I><BR> "; print "<B>E-mail:</B> <I><A HREF="mailto:$email">$email</A></I><BR> "; print "<B>Üzenet:</B> <I>$uzenet</I><BR> "; print "<SMALL><I>($datum)</I></SMALL> "; print "<HR> "; } close(FAJL); print <<END; </BODY> </HTML> END exit; Mint az látható, az űrlap-mezők alapértelmezett értéke a $nev , $email , és $uzenet lesz, melyek alapértelmezésben üresek, csak valamilyen hiba esetén kapták meg a hibás értékeket - ld. fentebb -, így könnyen módosíthatók a helytelenül
kitöltött mezők Az űrlap után a fájlt megnyitottuk olvasásra, majd egy while ciklus segítségével sorról-sorra - bejegyzésről-bejegyzésre - mentünk, majd a szétdarabolás után kiírtuk az adatokat, ezzel lezárva programunk fő részét. Két szubrutinra van még szükség, a datum()-ra, és a text2html()-re: sub datum { ($mp, $perc, $ora, $nap, $honap, $ev) = localtime(time); $ev += 1900; my @honapok = ("Január", "Február", "Március", "Április", "Május", "Június", "Július", "Augusztus", "Szeptember", "Október", "November", "December"); my $honap nev = $honapok[$honap]; return "$ev. $honap nev $nap $ora:$perc:$mp"; } sub text2html { $ [0] =~ s/&/&/g; $ [0] =~ s/</</g; $ [0] =~ s/>/>/g; $ [0] =~ s/ | | /<BR>/g; return $ [0]; } Ezek nem szorulnak részletekbe menő magyarázatra, a datum()
szubrutin az épp aktuális dátumot, és időpontot adja vissza, a text2html() pedig a már fentebb ismertetett cseréket hajtja végre. A sortörés cseréjénél először a Windows formátumú sorelválasztások kerülnek helyettesítésre, tehát a típusúak, és csak ez után a Unix, és a Mac formátumúak, mivel így elkerülhető, hogy egy Windows-os sorelválasztás helyén két <BR> elem jelenjen meg. Az előző rész elején, a CGI programok meghívásának módjánál szó volt az SSI-ről (Server Side Includes). Sorozatunk témaköréhez nem tartozik szorosan, ezért erről csak nagy vonalakban teszek említést, mintegy kiegészítésként. A múlt rész elején nagy vonalakban felvázoltam, nagyjából milyen folyamatok zajlanak le egy HTTP kérés esetén. A kapcsolat létrejötte után a kliens program elküldi a kérést, a szerver program pedig megkeresi a kért fájlt a szerver könyvtárstruktúrájában, majd elküldi azt. Itt jön a képbe az
SSI, mellyel bizonyos módosításokat hajthatunk végre HTML fájljainkon az elküldés előtt. Lehetőségünk van egy másik állomány tartalmának a beillesztésére, egy állomány utolsó módosításának idejének, vagy méretének a megjelenítésére, környezeti változók kiírására, parancssori utasítások végrehajtására, és CGI programok futtatására is. Egy HTML fájlon belül az alábbi SSI utasítással futtathatunk CGI programot: <!--#exec cgi="prgnev.cgi" --> Ebben az esetben a program kimenete -már ha van neki- az utasítás helyére fog kerülni. Más formák: Parancssorba kerülő utasítás: <!--#exec cmd="/bin/who" --> Környezeti változó kiírása: <!--#echo var="HTTP USER AGENT" --> Egy állomány méretének a kiírása: <!--#fsize file="program.zip" --> Egy fájl utolsó módosításának az ideje: <!--#flastmod file="valami.html" --> Másik fájl
beillesztése az oldalra (ha a beillesztendő fájlban is vannak SSI hivatkozások, akkor azok is végrehajtódnak, ha beillesztendő fájlba beillesztendő fájlban is találhatók hivatkozások, akkor azok is végrehajtódnak, és így tovább.): <!--#include file="valami.html" --> Mint látható, sok helyen szerepel a "file" paraméter. Ezekről tudni kell, hogy használatuk esetén a könyvtárstruktúrában csak egyre mélyebbre mehetünk, visszalépni, vagy a gyökérkönyvtárból indulni nem lehet. Ha a struktúrában az elérni kívánt fájl fentebb van, akkor a file="fájlnév" paraméter helyett a virtual="/elérési/út/fájlnév" forma is használható, azonban ekkor kötelezően a gyökérkönyvtárból kell indulni, visszalépni nem lehet! És még egy fontos dolog, hogy SSI esetén a gyökérkönyvtár nem a valódi gyökérkönyvtárat jelenti, hanem azt, amit a kliens-gép gyökérkönyvtárnak lát. Előfordulhat, hogy
SSI hivatkozásaink nem kerülnek végrehajtásra Ez azért lehet, mert a szervernek is támogatnia kell a szerver oldali beillesztéseket. Sok helyen a html kiterjesztésű fájloknál nem hajtódnak végre az utasítások, csak a .shtml esetén, ez azért van így, mert ebben az esetben a szerver programnak a html fájlokat nem kell elküldés előtt átvizsgálni, SSI hivatkozások után kutatva. Ezzel végére értünk a Perl programnyelvet tárgyaló sorozatunknak. Kiindulópontként mindenkinek ajánlom a wwwperlcom,valamint a www.perldoccom url-eket Eredményes munkát kívánok minden kedves olvasónak!