Üdv mindenkinek! Folytatjuk a Pascal nyelvet bemutató cikksorozatunkat. Ez alkalommal egy nem könnyű témába vágunk bele: a fájlkezelés csínját-bínját próbáljuk megérteni és megtanulni, hogy aztán a későbbiekben pofás programocskákat készíthessünk. Kezdjünk hozzá!

Mint korábban megtudtuk, a programok adatokat használnak, ám ezek az adatok a program futása közben a memóriában tárolódnak. A memória mérete azonban korlátozott, nem beszélve arról, hogy a program futása után kitörlődnek az adatok, vagyis elvesznek mindörökre. Ezen gondok megoldására vezették be a fájlt (file - adatállomány). A fájl kifejezés rendszerint valamely háttértárolón tárolt adatok összességét jelöli.

A Turbo Pascal fájl fogalmán két dolgot érthetünk:

  • eszköz (device) fájlok: a számítógépen található I/O eszközök (billentyűzet, képernyő, nyomtató) és a kommunikációs csatornák közvetlen elérését teszik lehetővé

  • lemez (disc) fájlok: a számítógép lemezegységein tárolt egymással összefüggő adatok együttesét jelölik. Ezeket tartalmuk alapján szöveges (text), típusos és típus nélküli fájlok csoportjára oszthatjuk.

A fájlkezelés lépései programozási nyelvtől függetlenek. A használni kívánt fájlt előbb meg kell nyitni, mivel csak így férhetünk hozzá a benne tárolt adatokhoz. Ezután az adatokat olvashatjuk a fájlból, vagy írhatjuk a fájlba. Miután befejeztük munkánkat, a fájlt le kell zárni, hogy annak adatai a háttértáron megőrzésre kerüljenek.

Nézzük külön-külön a különböző tartalmú és típusú fájlokat először röviden:

  • szöveges fájlok: ezek a fájlok olyan sorokból épülnek fel, amelyek karaktereket tartalmaznak, és a kozsivissza/soremelés (CR/LF) vezérlőkarakterek zárják le. Ezeknek a vezérlőkaraktereknek az ASCII kódjuk 13 és 10 és a sorok végét jelzik a fájlban. A szöveges fájlokat tetszőleges szövegszerkesztővel létrehozhatjuk és módosíthatjuk.


  • Ezek a fájlok elérésük és használatuk szempontjából a szekvenciális fájlokhoz tartoznak. Ez annyit jelent, hogy az egyes sorokat mindig a legelső sortól kezdve, egyesével olvashatjuk illetve írhatjuk. Kivéve, ha a fájlt a hozzáírás műveletéhez nyitottuk meg, mert ilyenkor az írás a fájl utolsó sora után kezdődik. A szöveges fájlt vagy csak olvashatjuk, vagy csak írhatjuk, tehát a fájl használata előtt el kell döntenünk, hogy mit szeretnénk csinálni.
  • típusos fájlok: olyan adathalmazok, amelyek jól definiált, azonos típusú összetevőkre, elemekre bonthatók. Ezen elemek típusa tetszőleges (kivéve fájl és objektum). A fájlban tárolt elemek a felírás sorrendjében 0-tól kezdve egyesével sorszámozódnak. A fájl feldolgozásánál ezen sorszám felhasználásával pozícionálhatunk az állomány valamely elemére. A típusos fájlok szerkezete valahogy így néz ki:

1. elem
2. elem
3. elem
.
.
.
n. elem

A szóhasználatban a rekord kifejezés terjedt el az elemek megnevezésére, de mi tudjuk, hogy a record kifejezés a Pascalban egy felhasználói adattípust jelent, tehát maradjunk az elem megnevezésnél.

A típusos fájlon végzett műveletek esetén, a művelet egysége az elem. A fájl méretét szintén a benne található elemek száma határozza meg. A típusos fájlok feldolgozása történhet szekvenciálisan illetve direkt módon egyaránt. A direkt módon történő feldolgozás lényegesen hatékonyabb, hisz a lényege annyi, hogy a fájlban mi pozícionálunk, ezáltal azon az elemen végezzük el műveleteinket amelyiken akarjuk. Ez a pozícionálás nem egyirányú, vagyis nem kell mindig a fájl elejéről indulni, bárhonnan bármerre mozoghatunk, mert minden elemnek van egy sorszáma, ami által elérhetjük.

  • típus nélküli fájlok: ezek használata nagy segítséget jelent az ismeretlen szerkezetű fájlok feldolgozásában. Amíg a szöveges fájlok sorelválasztó karaktereket tartalmaznak, a típusos fájlok adott típusú elemekből állnak, addig a típus nélküli fájlnál tetszőleges adatok olvashatók illetve írhatók. Mivel az adatforgalom a fájl és a program között mindenféle ellenőrzés nélkül megy végbe, gyors fájlkezelés valósítható meg. A típus nélküli fájlokkal végzett műveletekben egyetlen lépésben elérhető egység a blokk.

  • eszközök használata: a Pascal, ugyanúgy mint az operációs rendszer, a külső hardvert ( perifériákat: billentyűzet, képernyő, nyomtató) eszközökként kezeli. Programozói szempontból az eszköz egy fájl, amelyet ugyanazon eljárásokkal és függvényekkel érhetünk el, mint a lemez fájlokat. Ezen eszközök kezelésére általában szöveges fájlokat használunk, de bizonyos esetekben a típus nélküli fájl hatékonyabb megoldást biztosít. Az eszközök:

Fájlnév Szabványos eszköz Input vagy Output
AUX Auxiliary (a COM1 szinonim neve) I/O
COM1, COM2 Soros kommunikációs portok I/O
CON Konzol (I - billentyűzet, O - képernyő) I/O
LPT1, LPT2, LPT3 Nyomtató portok O
PRN Nyomtatók (LPT1 szinonim neve) O
NUL Nulla eszköz I/O

Miután röviden áttekintettük a különböző típusú fájlokat, most ismerkedjünk meg deklarálásaikkal, megnyitásukkal, műveleteikkel és lezárásukkal. A sorrendet nem változtatjuk, először megnézzük a szöveges fájlt, aztán a típusost, a típus nélkülit, majd a végén az eszközök használatát. Lássunk hozzá!

Szöveges fájl
  • azonosítás


  • ahhoz, hogy a programunkból szövegfájlt használjunk, deklarálnunk kell egy fájlváltozót, amely egyértelműen azonosítja a fájlt a programon belül. Ezen változó típusa Text.

    var fájl_változó: Text;

    Ezután a fájlváltozóhoz hozzá kell rendelni az operációs rendszer szintjén található fájlt, hogy a program tudja melyik fájllal kell dolgoznia.

    Assign(fájl_változó, fájl_név);

    Ezután mi a programon belül már csak a fájl_változót használjuk azonosításként, magával a fájllal nem kell törődnünk.
     

  • megnyitás:

  • a szövegfájlok megnyitására három módszer létezik. Mint azt már említettem vagy olvasásra, vagy írásra tudunk megnyitni egy szövegfájlt. Tehát a három kifejezés:

    reset(fájl_változó);

    Egy létező fájlt nyit meg, csak olvasásra. A megnyitás után az eof függvény true értékkel jelzi, ha a fájl üres. Ezen kívül a fájl elejére állítja az aktuális pozíciót (már megnyitott fájlon is elvégezhető).

    rewrite(fájl_változó);

    Egy új szövegfájlt hoz létre, vagy a létező fájlt újra írja (így annak teljes tartalma elvész). Az így megnyitott fájlokon csak az írás művelete hasznűlható. Az aktuális pozíció a fájl elejére kerül.

    append(fájl_változó);

    Egy létező fájlt nyit meg, csak hozzáírásra. Az aktuális pozíció a fájl végére kerül.
     

  • I/O műveletek: 

  • a fájlba történő írás és a fájlból történő olvasás a már jól ismert write, writeln, read, readln eljárások módosított változataival oldhatjuk meg. Mindegyik eljárásnál első paraméterként a fájl_változót kell megadni:

    write(fájl_változó, kifejezéslista);
    writeln(fájl_változó, kifejezéslista);

    A különbség a két eljárás között az, hogy a writeln eljárás az írási művelet végeztével sorvége (CR/LF) jelet is ír a fájlba.

    read(fájl_változó, kifejezéslista); readln(fájl_változó, kifejezéslista);

    A readln eljárás, ellentétben a read eljárással, a fájlból mindig teljes sort olvas be, így az aktuális pozíció a művelet után a következő sor eleje lesz.
     

  • a fájl és a sor végének ellenőrzése: 

  • Ha a szövegfájlt olvasásra nyitottuk meg, lehetőségünk van bizonyos ellenőrzések beépítésére, amelyek szükségesek ahhoz, hogy a programunk helyesen dolgozza fel a fájlt.

    A fájl végének ellenőrzésére az eof(fájl_változó) függvény szolgál. Ez egy logikai típusú eredményt ad vissza, ami jelzi, hogy az aktuális pozíció a fájl utolsó eleme után helyezkedik el (true), vagy sem (false). Szövegfájloknál a fájl végét két dolog is jelezheti: a fájlban egy end-of-file jelzőt találunk (ASCII 26), vagy elértük a fájl fizikai végét. A seekeof(fájl_változó) függvény akkor is a fájl végét jelzi (true), ha az aktuális pozíció és a fájl vége között már csak szóköz, tabulátor vagy sorvége karakterek vannak.

    A sorok végének ellenőrzésére szintén két függvényt használhatunk. Hasonlóan az előzőhöz az eoln(fájl_változó) függvény true értékkel tér vissza, ha az aktuális pozíció az end-of-line (sorvége - CR/LF) jelzőn áll. A seekeoln(fájl_változó) akkor is jelzi a sor végét, ha az aktuális pozíció és a sor vége között már csak szóköz vagy tabulátor karakterek vannak.

Típusos fájl
  • deklarálás:
  • var   fájl_változó: file of típus;

    A típus tetszőleges Pascal típus lehet, kivéve a file és az object típusokat. Tehát lehet egyszerű és összetett, beépített vagy felhasználói típus egyaránt.
    Ezután a már előbb ismertetett módon az assign paranccsal hozzárendelünk a fájl_változóhoz egy fájlnevet.
     

  • megnyitás

  • a megnyitás előtt nem kell meggondolnunk, hogy most olvasni vagy írni akarunk a fájlba, mert ez teljesen mindegy. Azt kell csak eldönteni, hogy egy létező fájlt akarunk megnyitni, vagy pedig egy új fájlt akarunk létrehozni vagy a létezőt felülírni.

    reset(fájl_változó); rewrite(fájl_változó);

    Mindkét megnyitási mód után az aktuális pozíció a fájl elejét jelöli.
     

  • műveletek

  • az adatok kiírására a write, míg az olvasásra a read eljárásokat használhatjuk.

    read(fájl_változó, változólista);

    Az aktuális pozíciótól kezdve beolvassa a fájl elemeit a változólistában megadott változókba. A művelet végrehajtása után az aktuális pozíció az utoljára beolvasott elem után helyezkedik el.

    write(fájl_változó, változólista);

    Az aktuális pozíciótól kezdve, a változólistában megadott változók tartalmát a fájlba írja. Az aktiális pozíció az utoljára kiírt elem után helyezkedik el. Ha az írási művelet megkezdésekor az aktuális pozíció a fájl végén volt, akkor a write eljárás a fájlt kibővíti a megadott elemekkel.

    Az elemek közvetlen elérését teszi lehetővé a seek eljárás:

    seek(fájl_változó, longint_index);

    Meghívása után az aktuális pozíció a longint_index paraméterben megadott pozíció lesz. Van még két függvény, ami a pozícionálásnál is felhasználható, és nagy segítséget nyújthat.

    filesize(fájl_változó)

    Ez a függvény longint típusú értékben adja meg a fájlban található elemek számát. Ha fájl üres, a visszaadott érték 0.

    filepos(fájl_változó)

    Szintén longint típusú értékben az aktuális pozíció indexét adja vissza. A fájlban az elemek sorszámozása 0-tól kezdődik. A fájl végénak elérésekor a filesize és a filepos függvények ugyanazt az értéket adják.

    Az utolsó eljárással a fájlunkat "lecsonkíthatjuk".

    truncate(fájl_változó);

    A művelet végrehajtása után az aktuális pozíció lesz a fájl vége. Így az aktuális pozíciótól a fájl végéig tartó részt levágjuk, amelynek tartalma elvész.
     

  • a fájl végének ellenőrzésére itt is használhatjuk az eof(fájl_változó) függvényt.
Típus nélküli fájl
  • deklaráció:

  • var   fájl_változó: File;

    Plusz az assign parancs a már jól megismert módon.
     

  • megnyitás

  • hasonlóan működik mint a típusos fájloknál, egy kis kiegészítéssel. Mint már mondtam a típus nélküli fájlokkal végzett műveletekben, egyetlen lépésben elérhető egység a blokk. A blokk mérete alapértelmezésben 128 bájt, de ez a fájl nyitásakor a reset és rewrite eljárások második paraméterével módosítható.

    reset(fájl_változó, blokk_méret); rewrite(fájl_változó, blokk_méret);
     
  • műveletek

  • a típus nélküli fájlok írása és olvasása szintén blokkban történik. A blockread eljárást olvasásra, a blockwrite eljárást pedig írásra használjuk:

    BlockRead(fájl_változó, puffer, cnt); BlockWrite(fájl_változó, puffer, cnt);

    A két eljárás paraméterezése teljesen megegyezik: természetesen meg kell adnunk a fájlt azonosító fájl_változót, az adatátvitelhez használt puffert (bármilyen típusú lehet) és az egy lépésben olvasni, illetve írni kívánt blokkok számát (cnt).

    A seek eljárás, ennek megfelelően, blokkonkénti pozícionálást hajt végre, illetve a filepos függvény az aktuális blokk sorszámával tér vissza. A filesize függvény pedig a fájl_fizikai_mérete div blokk_méret kifejezés értékével tér vissza. Ha a fájl hossza nem osztható maradék nélkül a blokk_mérettel, akkor a fennmaradó bájtok nem érhetőek el. Ezért, ha olyan programot kívánunk írni, amely tetszőleges fájlt fel tud dolgozni, akkor a blokk_méretet 1-nek kell választanunk. A truncate eljárás szintén használható.
     

  • a fájl végének ellenőrzésére szintén használhatjuk az eof(fájl_változó) függvényt.

Nem véletlenül nem említettem egyik típusnál sem a fájl lezárását. Ugyanis ez az eljárás mindegyik típusnál ugyanaz, bárhogyan is nyitottuk meg azt:

close(fájl_változó);
Eszközök

Ezen eszközök elésére általában szövegfájlokat használunk, de bizonyos esetekben a típus nélküli fájl hatékonyabb megoldást biztosít.

  • deklarálás

  • a fent ismertetett módon, atóll függően, hogy milyen típusú fájl akarunk használmi.

    Az összerendelés a következőképpen módosul: az assign parancsban a fájl_változó mellé nem egy fájlnevet kell írni, hanem a fenti táblázatban ismertetett eszközök valamelyikét.

    Minden egyéb műveletet, típustól függően az előbb ismertetett módon kell végrehajtani.

Hibakezelés

Ha a fájl nyitásakor valamilyen hiba lép fel (pl.: nem létező fájlt nyitunk meg olvasásra), akkor a programunk Runtime error xxx at yyyy:zzzz hibaüzenettel leáll. A Pascal lehetővé teszi, hogy az Input/Output (I/O) műveleteknél fellépő hibákat a programon belül dolgozzuk fel. A kritikus programrészletetet a {$I-} és {$I+} direktívák közé helyezve az IOResult függveny visszaadott értékéből következtethetünk a hiba megjelenésére.

Az IOResult függvény értékéből mindig csak a legutolsó I/O művelet lefolyására következtethetünk. A függvény hívása törli a belső állapotjelző értékét, így ha többször szeretnénk azt lekérdezni, akkor egy saját, Integer típusú változóban kell eltárolnunk az első híváskor visszaadott értéket. Az IOResult 0 értékkel tér vissza, ha a legutolsó I/O művelet sikeres volt, és valamilyen run-time hiba kódjával, ha nem.

A legkritikusabb pont a fájl megnyitása. Tehát itt egy programrészlet ami bemutattja, hogyan ellenőrizhetjük műveletünk sikerességét:

{$I-}   Reset(f);   Ior:=IOResult;   if Ior<>0 then   begin     WriteLn('A fájl nem létezik!');     Exit;  end; {$I+}
Ennyi.