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.

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.

### 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 ismertetett 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ítettük egyik típusnál sem a fájl lezárását. 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.

### HIBAFORRÁ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észletet 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+}`

#### DomTom