ugrás a tartalomhoz

Perl alapjai VI. - Fájlkezelés

Bártházi András · 2004. Okt. 26. (K), 22.45
Perl alapjai VI. - Fájlkezelés
Előző alkalommal a mintaillesztő kifejezésekről volt szó, s ennek kapcsán említettem, hogy a Perl egyik erőssége a szövegfeldolgozás. Szövegfeldolgozás azonban nem létezik fájlműveletek nélkül, hiszen az adatokat valahonnan be kell olvasni, s valahova ki kell menteni. Ebben a cikkben azt fogjuk áttekinteni, hogy milyen lehetőségeket ad a Perl a szöveges és bináris fájlkezelésre. Minden részletet nem fogunk tudni megismerni, de ígérem, így is bőven lesz olvasnivaló.

Szöveges fájlkezelés, bemenet, kimenet

Ebben a részben az alapokat, majd a különböző nemzeti kódolásokat, beleértve az utf8-at fogjuk megtekinteni.

Alapok

A szöveges fájlkezelés alapvetően a Perlben sincs másképpen, mint a többi nyelvben, az open és a close parancsok játszanak központi szerepet. Az egyik különbség azonban, hogy a megnyitott fájlokhoz rendelt azonosítókra a Perl egy külön változótípust használ. Erről a típusról eddig nem volt szó, csak és kizárólag a fájlműveletekben van szerepe. A többi típustól eltérően nincs előtte se dollárjel, se más, ezek nélkül kell használnunk az azonosítót. A "hagyományok" szerint ezeket a változókat végig nagybetűkkel írjuk, azonban ez nem követelmény.

A következő programrészlet megnyit egy szöveges állományt, és kiírja a sorait a képernyőre:

open(FILEID,'<proba.txt');
print <FILEID>;
close(FILEID);
Az első sorban a FILEID nevű (aminek lehetett volna bármilyen neve, ami egy változónak megengedhető, s lehetett volna kisbetűvel is írni, ahogy a korábbiakban említettem) fájlazonosítóhoz rendeljük hozzá az állomány megnyitásával magát az állományt (egy állomány több azonosítóhoz is hozzárendelhető). A későbbiekben ezzel az azonosítóval tudunk a "megnyitott" állományra hivatkozni. Az open második paramétere az fájlnév, melyben a UNIX környezetben megszokott kisebb-nagyobb jeleket használhatjuk a megnyitási mód meghatározására. Példánkban olvasásra nyitjuk meg az állományt, ezért < jelet írtunk. Megjegyzendő, hogy ha ezt elhagytuk volna a fájl neve elől, akkor is olvasásra nyílt volna meg az állomány. Amennyiben a > jelet használtuk, akkor írásra nyitódik meg a fájl, s ennek következményeként a tartalma egyből el is veszett volna. Ha ezt nem szeretnénk, akkor lehetőségünk van még hozzáfűzésre is megnyitni a fájlt, ehhez a >> jelet kell használnunk. Ezekre mindjárt nézünk példát is.

Az utolsó sorban megszüntetjük a hozzárendelést, azaz bezárjuk a fájlt. A megszüntetés nem szükséges, de illendő tevékenység, ha nem tesszük meg, akkor kilépéskor a Perl bezárja a nyitva maradt fájlokat.

A második sorban kerül beolvasásra és megjelenítésre a fájl tartalma. A kisebb és nagyobb jelek közé tett fájlazonosító szolgál egy sor beolvasására, gyakorlatilag ez a szöveges fájlból olvasás menete. Figyelnünk kell arra, hogy visszaadja a sortörés karakterét is (bármi legyen is az), tehát ha valóban csak a sor tartalmát szeretnénk, akkor egy chomp parancs nem árt majd előtte.

Ebben a sorban van egy trükk is, miszerint ha tömb környezetben hivatkozunk a fájl soraira, akkor az összes sor beolvasásra kerül, majd átadódik, szemben a skalár környezetben történő használattal, ahol csak a következő sor kerül beolvasásra. Példánkban azért kerül kiírása a teljes állomány, mert a print utasítás valójában egy tömböt (listát) vár, melynek összefűzve jeleníti meg az elemeit. Ha tehát
$sor=<FILEID>;
formában hivatkozunk a fájlra, akkor egy sort, ha pedig
@sorok=<FILEID>;
formában, akkor a teljes fájlt, soronként a tömb egyes elemeihez rendelve tölti be.

Az fájlok írását a következőképpen valósíthatjuk meg Perl-ben:

open(FILEID,'>proba.txt');
print FILEID "Hello World!";
close(FILEID);
Az első és az utolsó sor hasonlít az előző példára, itt mivel írni akarjuk a fájlt, ezért a > jelet használtuk a fájl neve előtt. A második sorban látható, hogy a fájlba írásra a print utasítást használtuk, mely után a fájl azonosító, majd - vessző nélkül! - a kiírandó lista következik. Az alapvető szöveges fájlműveleteket ezzel meg is ismertük, de van még mit tanulni.

A Perl nyelvben léteznek a standard kimenetre (alapértelmezett esetben képernyőre írás), a standard bemenetre (alapértelmezett esetben billentyűzetről bevitel) és a standard hibakimenetre (alapértelmezett esetben képernyőre írás) speciális, beépített fájlazonosítók. Ezek rendre STDOUT, STDIN és STDERR nevűek. Ezek használata teljesen megegyezik a többi állományazonosítóéval, a különbség annyi, hogy nem kell megnyitni ezeket az open, s nem kell lezárni a close paranccsal. A következő kis program bekér egy nevet a felhasználótól, majd köszönti. Amennyiben nem adtunk meg nevet, a hiba kimenetre ír ki egy hibaüzenetet.

print "Hogy hívnak? ";
chomp($nev=<STDIN>);
if ($nev ne '') {
  print STDOUT "Köszöntelek $nev!\n";
 } else {
  print STDERR "Nem adtál meg nevet!\n";
}
Ha ránézünk a programra, felfedezhetünk benne egy "hibát", az első print esetében nem írtuk oda, hogy milyen fájlba, hova szeretnénk kiírni az üzenetet, szemben a negyedik sorban levő print utasítással. Ilyenkor, ahogy arról már korábbi cikkek keretében szó volt, a Perl egyszerűen kiírja a szöveget, a STDOUT értéket veszi alapértelmezettnek.

A következő sor beolvas egy sort a standard bemenetről (azaz alapértelmezett módon a billentyűzetről). A "beolvas egy sort" konkrétan azt jelenti, hogy addig fogad karaktereket, amíg egy ENTERt nem ütünk le (vagy nem szakítjuk meg a programot). A leütött ENTER is a része lesz a bevitt értéknek, ezért is futtatunk le rajta egy chomp utasítást, mely levágja azt a sor végéről.

Végül egy if utasítás segítségével eldöntjük, írt-e be valamit a felhasználó, s ha igen, köszöntjük, ha nem, akkor pedig hibaüzenetet jelenítünk meg.

Mielőtt áttérnénk a nemzetközi vizekre, bemutatnék egy tipikus példát fájlfeldolgozásra, mely soronként beolvassa a fájlt, s valamilyen műveletet végez rajta. Jelen esetben megszámolja, hány olyan sora van, amelyben szerepel az, hogy "perl":

$darab=0;
open (F, "fájlnév.txt");
while (<F>) {
  $darab++ if /perl/;
}
close (F);
print "$darab előfordulás.\n";
A példából csak annyit magyaráznék meg, hogy a ciklusnak akkor lesz vége, mikor a fájl végére érkeztünk, s a cikluson belül az éppen aktuális sor mindig a $_ "topic" változóba kerül.

Nemzetközi fájlkezelés

A Perl 5.8.0 megjelenésével a régi standard fájlkezelő motort nagyrészt a PerlIO nevű motor váltotta fel, mely lehetővé tesz transzparens konverziókat a ki- és bemeneti műveletek során. Ezeket teljességében ebben a cikkben nem tekintjük át (egy egész cikket is kitesz a téma), de egy rövid ízelítőt fogunk látni belőle.

Az input és output szabályozásáért az open nevű "pragma" a felelős. A pragmák különböző dolgokra használhatóak, például lehetővé tesznek kötöttségeket a programozást illetően ("strict" pragma), stb. Az open pragma használata a következőképpen történik:

use open ':utf8';
use open IO => ':utf8';
use open OUT => ':utf8';
use open IN => ':utf8';
Ezt a négy sort együtt sohasem kell így használni, csak a lehetőségeket tekinthetjük át segítségükkel. Az első sor és a második sor azonos hatást vált ki: mind a kimeneti, mind a bemeneti műveletek során beállítja, hogy a kívülről érkező értékek utf8 kódolásuak. A harmadik sorban csak a kimenetet, a negyedik sorban pedig csak a kimenetet állítjuk utf8 kódolásúra. A Perl belső sztring ábrázolása Unicode az 5.6.0-s verzió óta, de automatikus konverziót végez minden alkalommal, ahol erre szükség van, így erről akár nem is lenne szükséges tudnunk. Nem vesszük észre azt, ha a programunk kódolása iso-8859-1 (vagy iso-8859-2), és mi egy ugyanilyen fájlba írunk, mivel a fájlműveletekhez ugyanazt a kódolást alkalmazza, ami a program kódolása (s amit a környezeti beállítások alapján határoz meg). Ha leírjuk egy programban, hogy

$nagyo="Ő";
akkor a $nagyo változóba, ha a memóriatartalmat nézzük, akkor a nagy Ő betű Unicode megfelelője fog kerülni. Mindez addig nem lényeges azonban, amíg nem kerülünk szembe nemzetközi környezettel, s nemzetközi szövegfeldolgozással, ahol lehet, hogy olvasunk egy iso-8892-2 kódolású fájlból, míg írnunk egy utf8 kódolásúba kell. A sztringműveletek, miután a memóriában már jó kódolással vannak az adatok, már jól működnek, lássuk, hogy pontosan mi is történik, hogy ha használjuk ezt az "open" pragmát. A következő program a fájlba kiír egy nagy Ő betűt utf8 kódolással, majd beolvassa, s kiírja hexadecimálisan, hogy milyen értéket olvasott be.

use open OUT => ':utf8';

open(O, ">perlio.txt");
print O chr(0x150); # Ő
close O;

open(I, "<perlio.txt");
$input=<I>;
printf "%#x\n", ord(substr($input,0,1)), "\n";
printf "%#x\n", ord(substr($input,1,1)), "\n";
close I;
Ha megnézzük, mi lesz a fájl tartalma egy hexadecimális editorral, akkor két byte-ot fogunk látni, az első a 0xC5, míg a második 0x90. Ez a hosszú Ő megfelelője utf8 kódolással. A beolvasás viszont nagyban függ attól, hogy milyen környezetben futtatjuk a scriptet. Ha iso-8859-2, vagy iso-8859-1 környezetben (talán manapság még ez a jellemzőbb), akkor 0xc5 és 0x90 értékeket kapjuk vissza is. Ha esetleg utf8 környezetben vagyunk, akkor a 0x150 értéket.

Az történik, hogy a 0x150 Unicode byte kódnak megfelelő hosszú Ő karaktert kiírjuk utf8 reprezentációban, majd visszaolvassuk úgy, mintha sima iso-8859-1 (vagy -2) kódolású lenne a fájl, s így két karaktert fogunk visszakapni. Ha beolvasni is helyesen szeretnénk a fájlt, akkor a következő program fog nekünk jól működni:

use open IO => ':utf8';

open(O, ">perlio.txt");
print O chr(0x150); # Ő
close O;

open(I, "<perlio.txt");
$input=<I>;
printf "%#x\n", ord(substr($input,0,1)), "\n";
close I;
Most azt kapjuk - bármely környezetben is legyünk -, hogy a beolvasott karakter egy nagy Ő, azaz 0x150. Programon "belül", legyen a beolvasott fájl bármilyen kódolásban, ha megfelelő kódolást állítottunk be a beviteli műveletekre, már helyesen tudunk dolgozni a sztringekkel.

A fent bemutatott 'utf8' sztringek helyett elég széles tárház áll rendelkezésünkre a különböző kódolásokból, gyakorlatilag bármely valószínűleg általunk használandó kódolást ismerni fog a Perl, amivel fordulunk hozzá.

Bináris fájlkezelés

A Perl alapvetően a szöveges fájlokat kezeli, de (teljeskörű) lehetőséget kínál a bináris fájlkezelésre is. A kimenet általában itt sincs másként, mint a szöveges fájloknál, itt is a print utasítást használhatjuk (illetve a syswrite-ot, erről mindjárt később). Az olvasásra és a fájlban történő mozgásra saját utasítások vannak.

Mielőtt rátérnénk ezek megismerésére, fontos szót ejteni a binmode parancsról, mely ide kapcsolódik. Ennek egy paramétere van, a fájlazonosító. Ha ezt a parancsot "végrehajtjuk" a fájlazonosítón, akkor binárisként fogja kezelni az állományt. Ez azokon a rendszereken (pl. Windows) fontos, ahol a fájlok olvasásakor egyébként konverzió történne, s a soremelések átalakulnának (pl. Windows esetén a két bájtos 0x0d+0x0a-ról 0x0a-ra).

Az olvasásra két utasítást használhatunk, az egyik a read, míg a másik a sysread. Mindkettő paraméterezése azonos, első paraméterük a fájlazonosító, második egy skalár változó, melybe az olvasás történni fog, s a harmadik egy hossz paraméter, amennyi karaktert be szeretnénk olvasni. Egy negyedik paraméter is megadható, mely arra szolgál, hogy megmondja, a skalár változó mely pozíciójába fogjuk beolvasni a karaktereket. Mind a két olvasási lehetőségre hatással vannak az előbbiekben nemzetközi fájlkezelés keretében ismertetettek, azaz előfordulhat, hogy a hosszba 2-t írunk, s mégis 3 bájt beolvasása történik meg: lehet, hogy épp az egyik karakter egy utf8 karakter volt, két bájtnyi kódolással.

A két utasítás között az a különbség, hogy a read bufferelt, azaz mondjuk úgy gyorsított előre tárazott fájlkezelést használ, míg a sysread enélkül olvas. Az előbbit sima állományoknál, az utóbbit rendszerfájloknál ajánlott használni, mint például különböző eszközök olvasása.

Ha rendszerfájlokat próbálunk meg írni, akkor a print helyett a syswrite használata a célszerűbb, mely nincs bufferelve, s így egyből kiírásra kerül az általunk kiírni kívánt adat. Paraméterezése a sysread paranccsal megegyező, azaz a fájlazonosítót, egy skalár változót és egy hosszt kell megadni, ekkor hossznyi karaktert fog kiírni a skalár változóból. Ha negyedik paraméterként megadunk egy értéket, akkor a skalár változóból attól a pozíciótól kezdve fogja ezt tenni.

A fájlok végét a eof függvénnyel ellenőrizhetjük, mely igaz értéket ad vissza, ha a paraméteréül kapott fájlazonosítóhoz tartozó fájl olvasásának a végére értünk.

Fontos parancs még a seek és a sysseek melyek a fájlban való mozgásra használhatóak. Három paraméterük van. Az első a fájl azonosítóját, a második egy pozíció értéket, a harmadik pedig egy módot határoz meg. Ha a harmadik paraméter értéke nulla, akkor a második paraméterben megadott bájtpozícióra ugrik (erre a műveletre nincs hatással a nemzetközi beállítás!), ha egy, akkor az aktuális pozíció, plusz a megadott érték helyre, s ha kettő, akkor a fájl vége, plusz a megadott érték. Utóbbi esetben általában negatív értéket szoktak meghatározni a programozók, s a második esetben is van ennek értelme.

A seek párja a tell, mely azt mondja meg, hogy melyik bájtpozíción vagyunk az adott fájlban.

Hasznos parancsok még a pack és az unpack, melyek segítéségével binárisra és binárisról kódolhatunk értékeket.

Végezetül fontos jeleznem, hogy "sima" és "sys" kezdetű függvényeket nem illik keverni, mert ha megtesszük, a bufferelés miatt nem várt következménye lehet a dolognak...

Összefoglalás

Nos, a mai cikkbe ennyi tudás fért bele, legközelebb folytatjuk a témát, s megismerkedünk a további lehetőségekkel ami a fájl és könyvtárkezelést illeti.
 
Bártházi András arcképe
Bártházi András
Az Emarsys-nál dolgozik vezető fejlesztőként, és az API-ért, integrációkért felelős termékmenedzserként. Szeret profi csapatban profit alkotni.
1

Nem nyitja meg a fájlt

Anonymous · 2005. Dec. 24. (Szo), 20.56

open(FILEID,'<proba.txt');
print <FILEID>;
close(FILEID);
Beirtam ezt es nekem nem nyitotta meg a fájlt :(! Csak egy sima mintha entert nyomtam volna es bejott megint a kurzor!persze a helyes fájlnévvel irtam be ami letezik nekem a HDD men!
2

Hibakezelés

Bártházi András · 2005. Dec. 25. (V), 12.38
Próbáld ki ezt:

open(FILEID,'<proba.txt') or die "$!";
print <FILEID>;
close(FILEID);
Ez annyival több, hogy ha sikertelen a fájl nyitása, kiírja a hiba okát és befejezi a program futását. A cikkben ismertetett példákból kimaradt a hibakezelés, bár elég fontos, hogy figyeljünk rá.

-boogie-
3

<Nincs cím>

Anonymous · 2005. Dec. 25. (V), 18.51
if ($nev ne '') {
itt mire kell a "ne" szocska? Vagy nem lehet ugy hogy &_nev????
4

Inkrementális

Bártházi András · 2005. Dec. 25. (V), 20.52
Tessék az elejéről olvasni. :) Ennek a végén van a magyarázat: http://weblabor.hu/cikkek/perlalapjai3

Amúgy a "not equal", azaz nem egyenlő rövidítése, és a karakteres nem egyenlő jele Perl-ben (a numerikus != párja).

-boogie-
5

áhhh

Anonymous · 2005. Dec. 29. (Cs), 22.46
nem tudom neked van e valami chated msn vagy hasonlo mert van par dolog ami eleg homalyos es jo lenne tisztazni de itt meg tul hosszu hogy leirjam!lecci segits:S!!
6

Perl levlista

Bártházi András · 2005. Dec. 29. (Cs), 23.44
A Perl levelezőlistát tudom ajánlani: http://perl.org.hu/mailman/listinfo/perl - vagy a fórumunkat.

-boogie-
7

file betöltés

GODy · 2010. Jún. 2. (Sze), 23.06
Hello, nekem az lenne a kérdésem, hogyan tudok betölteni olyan fájlokat, amelyek más szerveren vannak?

pl.:

open(F, "http://valami.mas.hu/file.xml");

mindig azt írja ki, hogy nem létezik ilyen fájl. Mit lehet ilyenkor tenni?
(A gépen lévő fájlokkal tökéletesen működik, csak nem szeretném minden alkalommal
letölteni a fájlt, hogy a benne lévő adatokkal dolgozhasson a program.)

Előre is köszi a segítséget!