ugrás a tartalomhoz

Perl alapjai VIII. - Könyvtárműveletek

Bártházi András · 2005. Már. 27. (V), 00.25
Perl alapjai VIII. - Könyvtárműveletek
Előző cikkünkben a Perl fájlkezelési lehetőségeivel ismerkedtünk meg, most ezt az irányt folytatva a Perl könyvtárkezelési képességeit tekintjük át, ezen belül is a könyvtárak tartalmának listázását, a könyvtárak manipulálását, s pár további lehetőséget, melyeket külső modulok segítségével érhetünk el. A cikk keretében pár hasznos trükkre is ki fogunk térni, melyek lehetővé teszik, hogy hatékonyan kezeljük könyvtárainkat.

A könyvtárak kezelése sokszor jöhet jól, akár a bemeneti adatok felderítése kapcsán, akár más feladatok esetén. A könytárak közötti váltás, egy könyvtár létrehozása, törlése az alapfeladatok közé tartozik, de nem mondható bonyolultnak egy könyvtár tartalmának meghatározása sem. Ezekre a Perl beépített függvényeket biztosít. Sokszor lehet azonban hasznos egy teljes rekurzív könyvtárszerkezet felderítése, vagy két könyvtár szinkronizálása, ezekre és sokminden másra különböző modulok állnak rendelkezésünkre.

Könyvtárak közötti váltás: chdir

A könyvtárak közötti váltás a chdir paranccsal lehetséges. Egyetlen opcionális paramétere van: azt kell megadni, hogy mely könyvtár legyen ezentúl az aktuális. Ha ezt a paramétert elhagyjuk, akkor az alapértelmezett (home) könyvtárra fog ugrani, melyet a HOME környezeti változóból próbál meg kideríteni. Ha ez nincs beállítva, akkor a LOGDIR környezeti változót ellenőrzi le. Amennyiben nem adunk meg paramétert, s ezek egyike sincs beállítva, nem fog történni semmi sem.

A chdir parancs visszatérési értéke igaz, ha a váltás sikeres, hamis, ha a váltás sikertelen.

chdir "/home/boogie/teszt";

Könyvtár létrehozása: mkdir

Könyvtár létrehozására az mkdir parancs szolgál. Egy vagy két paramétert adhatunk meg, az első, kötelező paraméter az új könyvtár neve (ha csak egy nevet adunk meg, akkor az aktuális könyvtárban lesz létrehozva, de megadhatunk relatív és abszolút útvonalakat is) kell, hogy legyen, a második, opcionális paraméter pedig a létrehozandó könyvtár hozzáférési maszkja. Amennyiben ezt elhagyjuk az alapértelmezett érték 0777. A maszk megadásakor ne feledjük, hogy nyolcas számrendszerben kell érteni, azaz a szám elé tegyünk egy 0-t is, hogy nyolcas számrendszerbelinek értelmezze a Perl (persze leírhatjuk a 0666 helyett azt is, hogy 438, ami a decimális megfelelője, de ettől inkább csak olvashatatlanabb kódot kapunk).

A mkdir parancs visszatérési értéke igaz, ha a könyvtár sikeresen létrejött, míg hamis, ha nem. Ez utóbbi esetben a $! változó is beállításra kerül, mely a hibakódot fogja tartalmazni (vagy ha sztring környezetben olvassuk ki, akkor a hiba nevét).

mkdir "/home/boogie/teszt";

Könyvtár törlése: rmdir

Egy könyvtárat az rmdir parancs segítségével törölhetünk. Egy opcionális paramétert vár, a törlendő könyvtár nevét. Ha ezt nem adjuk meg, akkor a $_ változóban levő könyvtárat törli. A könyvtárat csak akkor tudja törölni, ha az üres. Ha rekurzív könyvtártörlést szeretnénk, vagy magunknak kell azt megírnunk, vagy pedig használhatjuk például az operációs rendszer által biztosított parancsot is erre a feladatra.

A rmdir parancs visszatérési értéke igaz, ha sikerült törölni a könyvtárat, hamis, ha nem. Ekkor beállításra kerül a $! változó, értéke a hibakód, illetve sztring környezet esetén a hiba neve lesz.

rmdir "/home/boogie/teszt";

Könyvtárak listázása

A könyvtárak listázása egy fájl megnyitásához hasonlóan történik: alapvetően egy könyvtár megnyitás, olvasás és lezárás parancs áll rendelkezésünkre ehhez a feladathoz (és még további hárommal is megismerkedünk mindjárt). A könyvtár olvasása közben a benne levő fájlokat kapjuk vissza.

Egy könyvtár megnyitására a opendir parancs szolgál. A fájlkezelés kapcsán megismert open parancshoz nagyon hasonló: egy azonosítót kell megadni, majd a megnyitni kívánt könyvtárat. Például:

opendir DIR, "/home/boogie/teszt";
A megnyitott könyvtárat ezentúl a megadott azonosítóval érhetjük el (a példában DIR, de természetesen adhatunk neki bármely más nevet is). A könyvtár nevének végén nem kötelező / (UNIX/Linux) vagy \ (Windows) karaktereket megadni, saját ízlésünktől függ, hogy teszünk-e ilyet oda, vagy nem.

A könyvtárból egy fájl kiolvasására a readdir parancsot használhatjuk. Egy paramétere van, a könyvtár azonosító. Egy sztringet ad vissza, mely az adott könyvtárban levő következő fájl neve, ha skalár környezetben hívjuk meg. Ha a környezet listát vár, akkor az összes könyvtárban levő fájlt adja vissza (ha már olvastunk ki fájlokat, akkor az összes többit).

A visszaadott sztring csak és kizárólag a fájl nevét fogja tartalmazni, az útvonalát nem. Erre akkor kell figyelnünk, ha a megnyitott könyvtár nem az aktuális, s a fájlokkal szeretnénk kezdeni valamit: az útvonalat a kapott fájlnevek elé kell írnunk, hogy hozzáférhessünk azokhoz.

Jó tudni, hogy általában az operációs rendszerek alatt egy könyvtárnak nem csak a benne található könyvtárak és fájlok a részei, tartalmának lekérdezésekor egy magára a könyvtárra mutató fájlnevet (.) és egy a könyvtár szülőjére mutató fájlnevet (..) is visszakaphatunk (visszakapunk).

Egy könyvtárt bezárni a closedir paranccsal tudunk, mely felszabadítja az azonosítót és a hozzárendelt memóriaterületet. Ha a könyvtárat nem zárjuk be, akkor nem kapunk hibaüzenetet, a programunk végén ez automatikusan meg fog történni. Ha sok könyvtárral dolgozunk, a memóriafoglalás és a rend miatt érdemes egyedül lezárni a könyvtárakat.

A könyvtárkezelés kapcsán három további függvény is rendelkezésünkre áll, melyek a könyvtáron belüli pozícionálással foglalkoznak, vagyis a megnyitott könyvtárban levő éppen aktuális pozíciót (hányadik fájlnál járunk) olvashatjuk ki, vagy módosíthatjuk segítségükkel.

A rewinddir működése elég egyszerű, nem tesz mást, mint a könyvtár elejére ugrik, vagyis a helyzet ugyanaz lesz, mintha épp most nyitottuk volna meg a könyvtárat. Egy paramétere van, a könyvtárazonosító, melyhez az opendir paranccsal jutottunk hozzá.

A telldir segítségével azt tudhatjuk meg, hogy hányadik fájlnál járunk, egy paramétere van, a könyvtárazonosító. A seekdir ennek pont az ellenkezőjét teszi: valamely pozícióra ugrik. Az első paramétere a könyvtárazonosító, a második pedig a pozíció.

A könyvtárkezeléssel kapcsolatosan van pár trükk, melyet ha ismerünk, hatékonyabban végezhetjük el feladatainkat. A következőkben pár ilyet tekintünk át.

Abszolút fájlnevek

Az előbbiekben szó volt róla, hogy a readdir parancs csak és kizárólag a fájlnevet adja vissza. A következő függvény segítségével egy könyvtár tartalmát kaphatjuk vissza, teljes útvonalakkal:

sub listdir {
  my($dir)=@_;
  my(@files);
  opendir(DIR, $dir);
  @files=map($dir.$_,readdir(DIR));
  closedir(DIR);
  return(@files);
}
Természetesen az útvonal akkor lesz csak teljes, ha a megadott könyvtár paraméter is teljes útvonalat tartalmazott. Fontos, hogy a könyvtár nevének megadásakor a végén szerepeljen egy perjel, hogy amikor összefűzzük a könyvtárat és a fájlnevet, stimmeljen a végeredmény.

A trükk a függvény közepén látható: a map függvényt használtuk fel ahhoz, hogy a visszakapott fájlnevek mindegyikéhez hozzáfűzzük a könyvtárat is. Ahogy a korábbiakban már volt szó róla, lista környezetben a readdir nem egy fájlt, hanem az összeset fogja visszaadni, ezt használtuk ki ebben az esetben.

Csak fájlok, csak bizonyos fájlok

Elég gyakori feladat, hogy egy könyvtárban csak a fájlokra, vagy csak bizonyos tulajdonságú fájlokra vagyunk kíváncsiak. A következő függvény megszűri a könyvtár tartalmát, s csak a fájlokat adja vissza belőle:

sub dirfiles {
  my($dir)=@_;
  my(@files);
  opendir(DIR, $dir);
  @files=grep(-f $dir.$_,readdir(DIR));
  closedir(DIR);
  return(@files);
}
A szűrésre az arra kiválóan alkalmas grep parancsot használjuk, feltételéül pedig a "fájl-e?" operátort. Jelen változat csak a fájlnevet adja vissza, a könyvtárat nem fűzi elé, ha ezt szeretnénk, még egy az előzőekben látott módon kivitelezett map parancsot érdemes betenni a readdir elé. Ekkor így alakul a függvény:

sub dirfiles {
  my($dir)=@_;
  my(@files);
  opendir(DIR, $dir);
  @files=grep(-f,map($dir.$_,readdir(DIR)));
  closedir(DIR);
  return(@files);
}
Mivel a fájlnév már teljes útvonalat fog tartalmazni, ezért a -f operátornak nem kellett paramétert adnunk, az alapértelmezett $_ teljesen megfelel nekünk.

Ha például kiterjesztés szerint szeretnénk szűrni, a grep feltételét egészíthetjük ki a legegyszerűbben egy mintaillesztő kifejezéssel. A következő példa a könyvtár .pl kiterjesztésű fájlait adja vissza:

sub dirfiles {
  my($dir)=@_;
  my(@files);
  opendir(DIR, $dir);
  @files=grep(-f and /\.pl$/,map($dir.$_,readdir(DIR)));
  closedir(DIR);
  return(@files);
}
Talán azt érdemes ennek kapcsán megemlíteni, hogy mivel a mintaillesztő kifejezést is a $_-ra szeretnénk illeszteni, így ebben az esetben sem kellett megadnunk az illesztés tárgyát, ahogyan azt a -f operátor esetén is elhagytuk.

Rekurzív könyvtárszerkezet

Rekurzív könyvtárszerkezet összeállítására a cikk folyamán még látni fogunk egy nagyon jól használható (s a Perl disztribúciókban alapból telepített) modult, de nem árt, ha megismerjük, hogyan kell megoldani egy ilyen feladatot. A következő függvény ezt a feladatot valósítja meg:

sub recursivefiles {
  my($dir)=@_;
  my(@files);
  opendir(DIR, $dir);
  foreach(map($dir.$_,readdir(DIR))) {
    if (-f) {
      push @files,$_;
    }
    elsif (!/^\.\.?$/) {
      push @files,recursivefiles("$_/");
    }
  }
  closedir(DIR);
  return(@files);
}
Bár az egész foreach ciklus kiváltható lenne egy kifejezéssel, de az érthetőség miatt maradtam egy kicsit jobban kifejtett megoldásnál. Lássuk, mi mit csinál!

A foreach már egy "teljes" útvonalalat tartalmazó fájlnév csokrot kap, melynek minden elemén végig fog menni. Az első if egy egyszerű fájlvizsgálat, ha fájlról van szó, akkor a tömbbünkbe kerülhet a fájlnév. A második if, esetünkben elsif ágra akkor kerül sor, ha nem fájlról, azaz könyvtárról van szó. Ha ez a könyvtár nem ., vagy .. nevű, akkor szükségünk lesz a benne levő fájlokra, s ekkor ezen a ponton erre a könyvtárra meghívjuk a függvényt magát, majd a visszakapott fájllistát beletesszük a visszadandó listába.

További lehetőségek

A következőkben pár Perl modult tekintünk át, melyek valamilyen módon a könyvtárkezeléshez kapcsolódnak. További modulokat a search.cpan.org címen kereshetünk.

File::Find

A File::Find egy elsőre egyszerűnek tűnő, de elég rugalmas modul rekurzív könyvtárszerkezetek kezelésére. Két új függvényt biztosít, a find és finddepth függvényeket. Az utóbbi az első egy speciális változata.

A find parancs alapvetően a paraméteréül kapott könyvtárakat nézi végig, s minden fájlra, amit talál, meghív egy (paraméterként megadott) függvényt. Íme egy példa:

use File::Find;
find(\&wanted, ".");
sub wanted {
    print "$_\n";
}
Az első sorban jeleztük, hogy a modul szolgáltatásait használni szeretnénk. A második sorban meghívtuk a find parancsot, úgy, hogy a jelenlegi könyvtár (.) tartalmán menjen végig rekurzívan, s minden talált fájl esetén futtassa le a wanted függvényt. A függvényben ezután kiíratjuk az aktuális fájlnevet.

A megadott függvényben három "speciális" változó áll rendelkezésünkre. A $_ alapértelmezett esetben az aktuális fájlnevet tartalmazza, könyvtár nélkül. A fenti példában látható $File::Find::name a fájlnévben a könyvtárnevet is szerepelteti, a $File::Find::dir pedig csak az aktuális könyvtárat tartalmazza. Ezeket a változókat ne módosítsuk!

Harmadik és további paraméterként további könyvtárakat is felsorolhatunk.

Az első paraméter lehet egy hash is, mely használat esetén szabályozhatjuk a függvény működését különböző kérdésekben. Lássunk erre is egy példát:

use File::Find;
find({ wanted=>\&wanted, no_chdir=>1 }, ".");
sub wanted {
    print "$_\n";
}
A futtatandó függvényt továbbra is be kell állítanunk, erre a wanted kulcsszóval van lehetőségünk. A no_chdir a könyvtárakba lépkedést szabályozza, ebben az esetben kapcsolja ki. A find függvény alapértelmezett esetben a chdir parancs segítségével könyvtárat vált, s belép az éppen megtalált könyvtárba. Ennek kapcsán helyben különböző műveleteket tudunk elvégezni, anélkül, hogy az útvonallal törődnünk kellene. Ha ezt a viselkedést ki szeretnénk kapcsolni, használhatjuk a no_chdir paramétert. Beállításával egy másik változás is életbe lép, a $_ értéke ezúttal a könyvtárat is tartalmazni fogja.

A modul több további lehetőséget is kínál, ezek megismeréséhez a dokumentációja jó kiindulópont lehet.

File::DirSync

A File::DirSync modul segítségével két könyvtár tartalmát szinkronizálhatjuk, azaz érhetjük el, hogy ugyanaz legyen a tartalmuk. A cél a leghatékonyabb, legkevesebb művelettel, leggyorsabban végrehajtott szinkronizáció.

Egy példa a használatára:

use File::DirSync;

my $dirsync = new File::DirSync {
  verbose => 0,
  nocache => 1,
  localmode => 1,
  src => "/home/boogie/teszt",
  dst => "/www/barthazi.hu/teszt"
};

$dirsync->dirsync();
Ez a kis program a megadott könyvtárakat fogja egymással szinkronizálni. Fájlokat és könyvtárakat másol vagy töröl, időbélyegeket állít be, szimlinkeket hoz létre vagy távolít el. A forrás könyvtár állapotát átmeneti tárban rögzíteni tudja, ezzel még hatékonyabbá téve a szinkronizációt.

Paraméterek segítségével szabályozhatjuk a részletességet, az átmeneti tár használatát, hogy csak helyi, vagy "felmountolt" fájlokat is vizsgáljon-e. Akár egyesével is másoltathatunk, megadhatunk kihagyandó fájlokat, könyvtárakat (pl. "CVS"), s lekérdezhetjük a legutóbbi szinkronizáció során történteket (mely fájlok kerültek frissítésre, eltávolításra, lettek figyelmen kívül hagyva, illetve melyek esetén jelentkezett valamely hiba). Egy külön parancs segítségével az átmeneti tárat felépíthetjük, azaz a szinkronizációs adatbázist még a szinkronizáció előtt elkészíthetjük, ezáltal is javítva annak hatékonyságát.

A modulról további információkat a dokumentációjában találhatuk.

Összefoglalás

A jelenlegi alkalommal a könyvtárakkal kapcsolatos lehetőségeket tekintettük át, megismerkedtünk pár trükkel, s két modullal is. Ha utána szeretnénk nézni, milyen modulok állnak rendelkezésünkre, a search.cpan.org nyújthat ebben segítséget, a File és Dir szavakra érdemes rákeresni. Legközelebb a tervek szerint a Perles adatbáziskezelés lehetőségeinek alapjait tekintjük át.
 
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.