Woche 3

# Tag 17

# Dateien und Verzeichnisse verwalten

In Kapitel 15, »Dateien und E/A«, haben wir uns damit beschäftigt, wie man in Perl aus Dateien liest oder in Dateien schreibt. Heute wollen wir uns weiteren Aspekten der Dateiverwaltung zuwenden - insbesondere den verschiedenen Möglichkeiten, mit denen Sie über Ihr Skript das Dateisystem beeinflussen können. Heute lernen Sie,

- wie man Dateien verwaltet, das heißt, wie man sie entfernt, umbenennt, kopiert oder verknüpft und wie man Zugriffsberechtigungen ändert,
- wie man mit Verzeichnissen arbeitet, das heißt, wie man durch das Verzeichnissystem navigiert, wie man Verzeichnisse erzeugt und wieder entfernt und
- wie man auf Gruppen von Dateien zugreift und wie man Verzeichnis-Handles verwendet,

## Dateien verwalten

Mit Perl können Sie nicht nur aus Dateien lesen und in Dateien schreiben, sondern Sie können die Dateien auch verwalten - so wie Sie es üblicherweise von der Befehlszeile oder einem Datei-Manager aus tun: Sie können Dateien umbenennen, löschen, Zugriffsrechte ändern oder Verknüpfungen zu Dateien erstellen. Für jede dieser Aufgaben greifen Sie auf verschiedene vordefinierte Perl-Funktionen zurück, die ich Ihnen in diesem Abschnitt vorstellen möchte.

### Dateien umbenennen

Um einen Dateinamen durch einen anderen zu ersetzen, ohne dabei den Inhalt der Datei zu beschädigen, rufen Sie die Funktion `rename` mit zwei Argumenten auf: dem alten Namen der Datei und dem neuen Namen:

    rename meinedatei, meinedatei.bak;

Existiert bereits eine Datei mit dem gleichen Namen wie die neue Datei, wird diese von Perl überschrieben. Sind Sie an Unix gewöhnt, stellt dies kein Problem für Sie dar. Arbeiten Sie jedoch auf einem Windows- oder Mac-Rechner, gilt es vorsichtig zu sein! Es gibt keine Sicherheitsabfragen, wenn Sie eine bestehende Datei überschreiben. Deshalb sollten Sie vielleicht besser sicherstellen, dass es für den neuen Namen nicht bereits eine gleichnamige Datei gibt (mit einem der Dateitests wie `-e`), bevor Sie Ihre Datei umbenennen.

Die `rename`-Funktion liefert `1` oder `0` zurück (wahr oder falsch), je nachdem ob Sie Ihre Datei erfolgreich umbenennen konnten oder nicht. Um eine Datei umbenennen zu können, müssen Ihnen (oder dem Benutzer Ihres Skripts) die entsprechenden Zugriffsrechte eingeräumt sein.

### Verknüpfungen erstellen und Verknüpfungen nachfolgen

In der Unix-Version von Perl gibt es zwei Funktionen zum Erstellen von Verknüpfungen zwischen Dateien: `link` und `symlink`, mit denen harte beziehungsweise symbolische Verknüpfungen (entsprechend dem Unix-Befehl `ln`) erstellt werden. Harte Verknüpfungen dienen dazu, eine Datei mit mehr als einem Namen zu erzeugen. Wenn Sie einen beliebigen dieser Namen (einschließlich der Originaldatei) löschen, existiert die Datei weiter - solange wie es noch Namen für die Datei gibt. Symbolische Verknüpfungen sind Verweise auf andere Dateien. Wird die Verknüpfung gelöscht, bleibt die Datei bestehen, löschen Sie hingegen die Originaldatei, zeigt die symbolische Verknüpfung ins Leere. Nicht alle Unix-Systeme unterstützen symbolische Verknüpfungen.

Beide Funktionen, `link` und `symlink`, erfordern zwei Argumente: den Name der Originaldatei, für die eine Verknüpfung erstellt werden soll, und den Namen der neuen Verknüpfung. Sehen Sie dazu folgendes Beispiel:

    link datei1, datei2;

Beide Funktionen liefern im Erfolgsfall `1` (wahr) und ansonsten `0` (falsch) zurück.

Weder hart noch symbolisch verknüpfte Dateien stellen für Perl ein Problem dar. Wenn Sie `open` mit einem Dateinamen verwenden, der eine Verknüpfung darstellt, wird statt der Verknüpfung die Originaldatei geöffnet. Mit Hilfe des Dateitests `-l` können Sie prüfen, ob eine bestimmte Datei eine symbolische Verknüpfung ist oder nicht. Anschließend können Sie die Verknüpfung mit Hilfe der Funktion `readlink` zu der ursprünglichen Position der Datei zurückverfolgen:

    if (-l $dat) {                        # dat ist eine Verknüpfung
       $ursprungsdatei = readlink $dat;   # ermittelt die echte Datei
    }

Beachten Sie, dass Sie mit `readlink` nur den relativen Pfadnamen der Originaldatei zu ihrer symbolischen Verknüpfung erhalten. Das bedeutet, dass Sie entweder diesen Pfad zu einer vollen Pfadangabe erweitern oder in das Verzeichnis mit der symbolischen Verknüpfung wechseln müssen, um den Pfadnamen der Originaldatei zu verwenden. Wie Sie Verzeichnisse wechseln, erfahren Sie weiter hinten in diesem Kapitel.

Verwenden Sie Perl für Windows? Dann können Sie weder mit harten noch mit symbolischen Verknüpfungen arbeiten. Sie können aber das Modul `Win32::Shortcut` verwenden, um Windows-Explorer-Verknüpfungen zu erzeugen.

In MacPerl gibt es zwar keine Entsprechung zu den harten Verknüpfungen, doch funktionieren die symbolischen Verknüpfungen ganz analog zu den Mac-Aliasen. Die Funktion `symlink` erzeugt einen Alias, der Dateitest `-l` liefert wahr zurück, wenn die Datei ein Alias ist und `readlink` ermittelt die Position der Datei, mit der der Alias verknüpft ist.

### Dateien und Verknüpfungen entfernen

Sie sind mit einer Datei fertig und sind sich sicher, dass Sie sie nicht weiter benötigen. Wie werden Sie diese Datei jetzt wieder los? Dazu steht Ihnen die Funktion `unlink` zur Verfügung (trotz ihres Namens entfernt sie sowohl Verknüpfungen als auch Dateien). Sie entspricht in ihrer Funktionsweise dem Unix-Befehl `rm` oder dem DOS-Befehl `del`. Allerdings verschiebt diese Funktion die Datei nicht in einen Papierkorb wie unter Windows oder beim Mac. Wird eine Datei gelöscht, wird sie unwiederbringlich entfernt. Deshalb sollten Sie Vorsicht walten lassen, um nicht aus Versehen Dateien zu löschen, die Sie eigentlich noch benötigen.

Die Funktion `unlink` übernimmt ein Listenargument, bei dem es sich entweder um einen einzigen Dateinamen oder um eine ganze Liste von Dateinamen handeln kann. Als Rückgabe liefert Sie die Anzahl der gelöschten Dateien zurück.

    unlink temp, config, foo;

Beachten Sie, dass Perl auf manchen Systemen - allen voran Unix - bedenkenlos auch Nur-Lesen-Dateien löscht. Vorsicht ist also geboten, denn der Nur-Lesen-Status einer Datei bezieht sich lediglich darauf, dass der Inhalt dieser Datei nicht geändert werden darf, und nicht darauf, ob der Dateiname verschoben oder gelöscht wird.

Wird die Funktion `unlink` auf eine harte Verknüpfung angewendet, wird diese Verknüpfung entfernt. Andere harte Verknüpfungen zu der Datei bleiben bestehen. Auch im Falle einer symbolischen Verknüpfung wird die Verknüpfung entfernt, die Originaldatei bleibt bestehen. Mit `readlink` können Sie symbolischen Verknüpfungen nachfolgen. Beachten Sie, dass nach dem Löschen einer Datei, zu der symbolische Verknüpfungen bestehen, alle symbolischen Verknüpfungen zu der Datei ins Leere zielen.

Sie können mit `unlink` keine Verzeichnisse entfernen. Wie Sie Verzeichnisse löschen, erfahren Sie im Abschnitt »Verzeichnisse verwalten und wechseln«.

### Weitere Operationen

Es gibt noch eine Reihe weiterer dateibezogener Funktionen, die ich Ihnen kurz vorstellen möchte. Eine ausführlichere erspare ich mir, da sich die betreffenden Operationen von Plattform zu Plattform unterscheiden (oder unter Umständen unter Windows oder Mac gar nicht existieren). In Tabelle 17.1 sind weitere Dateiverwaltungsfunktionen aufgeführt, die auf Unix-Systemen verfügbar sind und mit Unix-Befehlen korrspondieren. Wenn Sie eine der Funktionen aus Tabelle 17.1 verwenden möchten, schauen Sie in der Dokumentation zu Ihrer Perl-Version nach, ob diese Funktion unterstützt wird oder nicht (oder ob es für die betreffende Operation anderweitig Unterstützung gibt - zum Beispiel durch ein plattformspezifisches Modul).

Weitere Informationen zu diesen Funktionen finden Sie in der perlfunc-Manpage.

Funktion | Was sie bedeutet
--- | ---
`chmod` | Ändert die Zugriffsrechte für die Datei `(Lesen`, `Schreiben`, `Ausführen`, `für Welt`, `Gruppe` oder `Besitzer`). Windows und Mac haben eingeschränkte Versionen dieses Befehls, der nur Zugriffe von 0666 (`Lesen` und `Schreiben`) und 0444 (`Nur Lesen` oder `gesperrt`) akzeptiert.
`chown` | Ändert den Besitzer dieser Datei (nur Unix)
`fileno` | Liefert den Dateideskriptor für die Datei zurück
`utime` | Ändert die Zeitangaben für eine Datei

Tabelle 17.1: Weitere Dateiverwaltungsfunktionen

Um unter Windows und Windows NT Dateizugriffsberechtigungen und -attribute zu verwalten, können Sie auch die Module `win32::File` und `win32::FileSecurity` verwenden. Diese ermöglichen es Ihnen, Dateiattribute und NT-Nutzerrechte zu prüfen und zu ändern.

## Verzeichnisse verwalten und wechseln

So wie Sie in Ihren Perl-Skripten mit Dateien in der gleichen Weise arbeiten, wie Sie es von der Shell oder der Befehlszeile aus tun, so können Sie in Ihren Perl-Skripten auch durch das Dateisystem navigieren und Verzeichnisse verwalten (im Sprachgebrauch mancher Betriebssysteme spricht man auch von Ordnern statt von Verzeichnissen). Sie können das aktuelle Verzeichnis ermitteln, das aktuelle Verzeichnis wechseln, den ganzen Inhalt des Verzeichnisses oder einen Teilbereich davon auflisten, und Sie können Verzeichnisse selbst anlegen oder löschen. In diesem Abschnitt möchte ich auf einige Aspekte bei der Ausführung dieser Operationen mit Perl eingehen.

### Verzeichnisse wechseln

Jedes Perl-Skript kennt sein aktuelles Arbeitsverzeichnis, das heißt das Verzeichnis aus dem das Skript aufgerufen wurde (wenn Sie gewohnt sind, mit einem befehlszeilenorientierten System zu arbeiten, wird Sie dies nicht überraschen). Auf dem Mac, der mit Droplet-Skripten arbeitet, ist das aktuelle Arbeitsverzeichnis das Verzeichnis, in dem sich das Droplet selbst befindet.

Um das aktuelle Verzeichnis von einem Perl-Skript aus zu ändern, verwenden Sie die Funktion `chdir`:

    chdir "bilder";

Wie schon bei der Verwendung von Verzeichnisnamen im Aufruf der `open`-Funktion sollten Sie darauf achten, dass Sie die Pfadangaben korrekt angeben und die Eigenheiten der verschiedenen Systeme berücksichtigen. Relative Pfadangaben, wie in diesem Beispiel, sind in Ordnung. Obiges Beispiel ändert das aktuelle Arbeitsverzeichnis in das Verzeichnis `bilder`, das unter dem gleichen Verzeichnis angeordnet ist, aus dem das Skript ursprünglich aufgerufen wurde (puh!).

Ist es möglich, herauszufinden, in welchem Verzeichnis Sie sich momentan befinden? Ja, aber wie Sie dazu vorgehen, hängt von dem System ab, auf dem Sie gerade arbeiten. Unter Unix stellen Sie das aktuelle Verzeichnis fest, indem Sie die schrägen Anführungsstrich-Operatoren verwenden, um den Befehl `pwd` aufzurufen (mehr zu den schrägen Anführungszeichen in Kapitel 18, »Perl und das Betriebssystem«):

    $curr =`pwd`;

Diese Methode läßt sich auch auf dem Mac anwenden. Unter Windows erhalten Sie das aktuelle Arbeitsverzeichnis mit der Funktion `Win32::GetCWD`. Der beste Weg, das aktuelle Arbeitsverzeichnis zu ermitteln - ein Weg, der übrigens auf allen Plattformen gleich gut funktioniert - führt allerdings über das Modul `Cwd`, das Teil der Standardbibliothek ist. Das `Cwd`-Modul stellt Ihnen die `cwd`-Funktion zur Verfügung, die das aktuelle Arbeitsverzeichnis zurückliefert (unter Unix führt `cwd` übrigens einfach ``pwd`` aus, so dass Sie auch hier bezüglich des Ergebnisses sicher sein können):

    use Cwd;
    $curr = cwd();

### Dateien auflisten

In Perl gibt es zwei Wege, eine Liste der Dateien in einem Verzeichnis erstellen zu lassen: Die erste Methode verwendet Platzhalter, mit deren Hilfe Sie eine Liste von Dateien erzeugen können, die einem bestimmten Muster entsprechen. Die zweite Methode verwendet die Funktionen `opendir` und `readdir`, die beide eine komplette Liste aller Dateien in einem Verzeichnis ausgeben.

#### Platzhalter in Dateinamen

Diese Technik, die man im Englischen als Datei-»Globbing« bezeichnet, erlaubt es Ihnen, eine Liste aus Dateien des aktuellen Verzeichnisses zu erstellen, die einem bestimmten einfachen Muster entsprechen. Wenn Sie jemals mit einem befehlszeilenorientierten System gearbeitet und Dateien mit Mustern wie `*.txt` oder `*foo*` aufgelistet haben, dann haben Sie schon mit Datei-Globbing gearbeitet (auch wenn Sie vielleicht nicht wußten, dass man diese Technik so nennt).

> Der Begriff »glob« ist - falls Sie es nicht schon geraten haben - von Unix entlehnt. Verwenden Sie niemals einen einfachen Begriff, wenn es auch kompliziert geht.

Beim Datei-Globbing nutzen Sie das Platzhalterzeichen `*`, `um` einen bestimmten Satz von Dateinamen zu definieren. Verwechseln Sie das Datei-Globbing aber nicht mit den regulären Ausdrücken - beim Datei-Globbing steht das `*` für ein beliebiges Zeichen (keines oder mehrere), und es ist das einzige Sonderzeichen, das Sie verwenden können. Als Ergebnis erhalten Sie eine Auflistung aller Dateinamen in dem aktuellen Verzeichnis, die dem Muster entsprechen. So liefert zum Beispiel `*.html` alle Dateinamen zurück, die auf die Extension `.html` auslauten, und `*foo*` alle Dateien, die im Namen selbst die Zeichenfolge `foo` enthalten. Sie können diese Dateinamen dann verwenden, um auf den einzelnen Dateien bestimmte Operationen auszuführen.

Perl erzeugt Datei-Globs auf zwei Wegen: mit dem Operator `<>`, der zwar aussieht wie der Eingabeoperator, aber keiner ist, oder mit der `glob`-Funktion.

Wenn Sie den `<>`-Operator verwenden, müssen Sie das Muster innerhalb der spitzen Klammern angeben:

    @dateien = <*.pl>;

Dieser Ausdruck, bei dem `<>` in einem Listenkontext verwendet wird, liefert eine Liste aller Dateien mit der Extension `.pl` zurück. Wenn Sie `<>` in einem skalaren Kontext verwenden, erhalten Sie jeden Dateinamen separat (wie bei der Eingabe), und wenn Sie den Operator innerhalb einer `while`-Schleife verwenden, werden die Dateinamen nacheinander der `$_`-Variablen zugewiesen, ebenfalls wie bei der Eingabe).

So gibt zum Beispiel das folgende Codebeispiel eine Liste aller Dateien in dem aktuellen Verzeichnis aus, die die Extension `.txt` haben:

    while (<*.txt>) {
       print $_, "\n";
    }

Verwechseln Sie den Globbing-Operator `<>` nicht mit dem `<>`-Operator für die Eingabe - sie sehen nur äußerlich gleich aus, arbeiten aber in ganz unterschiedlicher Weise. Letzterer benötigt einen Datei-Handle als Argument und liest den Inhalt dieser Eingabedatei. Ersterer liefert Strings mit den Dateinamen aus dem aktuellen Verzeichnis, die dem Muster entsprechen.

Da man die beiden Operatoren für das Globbing und für die Eingabe leicht verwechseln kann, gibt es noch die `glob`-Funktion. Damit wird das Globbing für alle deutlich und die Verwechslungsgefahr gebannt. Bei der `glob`-Funktion müssen Sie das Filtermuster in Anführungszeichen setzen:

    @dateien = glob "*.pl";

Das Ergebnis ist das gleiche. Auch hier erhalten Sie eine Liste der Dateien, die auf `.pl` enden (in einem skalaren Kontext erhalten Sie die Dateien eine nach der anderen). In modernen Perl-Skripten sollte man dem Datei-Globbing mit der `glob`-Funktion den Vorzug geben.

### Verzeichnislisten

Es gibt noch einen zweiten Weg, um eine Liste der Dateien in einem Verzeichnis zu erhalten, und der führt über die Verzeichnis-Handles. Diese erinnern stark an die Datei-Handles und verhalten sich auch so. Sie beziehen sich aber ausschließlich auf Verzeichnisse. Wenn Sie ein Verzeichnis mit Hilfe eines Verzeichnis-Handles einlesen, erhalten Sie eine vollständige Liste aller Dateien in dem Verzeichnis, einschließlich der versteckten Dateien, die mit einem Punkt beginnen (die Sie durch Datei-Globs nicht erhalten), sowie `.` und `..` für das aktuelle und das übergeordnete Verzeichnis in Unix und Windows (der Mac erkennt diese Zeichen nicht als Elemente des aktuellen Verzeichnisses; Sie können statt dessen `:` für das aktuelle und `::` für das übergeordnete Verzeichnis verwenden).

Verzeichnis-Handles werden auf die gleiche Art und Weise geöffnet und geschlossen wie Datei-Handles, nur dass man die Funktionen `opendir` und `closedir` verwendet. Die Funktion `opendir` übernimmt zwei Argumente: den Namen eines Verzeichnis- Handles (von Ihnen gewählt) und das Verzeichnis, das ausgegeben werden soll.

    opendir(DIR, ".") or die "Verzeichnis kann nicht geöffnet werden: $!\n";

Wie bei der `open`-Funktion sollten Sie stets das Ergebnis prüfen und das Skript gegebenenfalls mit Hilfe der `die`-Funktion in Würde sterben lassen (die Variable `$!` ist hier ebenfalls sehr hilfreich).

Bezüglich der Namensgebung gelten für die Verzeichnis-Handles (hier `DIR`) die gleichen Regeln wie für die Datei-Handles. Verzeichnis-Handles werden vollkommen unabhängig von den Datei-Handles betrachtet - Sie können einem Verzeichnis- Handle den gleichen Namen geben wie einem Datei-Handle, es wird keine Konflikte geben.

Sobald ein Verzeichnis-Handle geöffnet ist, können Sie mit `readdir` daraus lesen. Wie schon der Eingabeoperator `<>` liefert auch `readdir` in einem skalaren Kontext einen einzelnen Dateinamen zurück und in einem Listenkontext eine Liste aller Dateinamen. Das folgende Codefragment öffnet zum Beispiel das aktuelle Verzeichnis (das »`.`«- Verzeichnis unter Unix und Windows; Mac-Anwender schreiben statt dessen »`:`«) und gibt eine Liste des Verzeichnisses aus:

    opendir(CURR, ".") or die "Verzeichnis kann nicht geöffnet werden: $!\n";
    while (defined($file = readdir(CURR))) {
       print "$file\n";
    }

Beachten Sie, dass `readdir` - im Gegensatz zum Eingabeoperator `<>` - keine automatischen Zuweisungen an die Variable `$_` vornimmt. Sie müssen den Code dazu selbst in die `while`-Schleife aufnehmen oder (wie ich) eine temporäre Variable verwenden.

Mit `readdir` erhalten Sie eine Liste aller Dateien und Unterverzeichnisse in dem Verzeichnis. Wenn Sie bestimmte Dateien (versteckte Dateien, `.` oder `..`) herausfiltern wollen, müssen Sie dazu einen Vergleich mit einem regulären Ausdruck anschließen.

Sobald Sie das Verzeichnis-Handle nicht mehr benötigen, können Sie es mit der `closedir`-Funktion schließen:

    closedir(CURR);

### Verzeichnisse anlegen und löschen

Sie können aus Ihren Perl-Skripten heraus sogar neue Verzeichnisse anlegen und nicht mehr benötigte Verzeichnisse löschen. Die Funktionen dazu lauten `mkdir` und `rmdir`.

Die Funktion `mkdir` übernimmt zwei Argumente: den Namen eines Verzeichnisses (oder Ordners) und einen Satz an Zugriffsberechtigungen. Unter Unix beziehen sich die Zugriffsberechtigungen auf die Standardzugriffsbits des `chmod`-Befehl im Oktalformat (0 plus drei Zahlen von 0 bis 7). Unter Windows und Mac ist das Argument der Zugriffsberechtigung zwar gefordert, aber nicht sinnvoll. Verwenden Sie einfach `0777` als Argument, und Sie dürften keine Probleme bekommen:

    mkdir temp, 0777;

> `0777` ist eine Oktalzahl, die sich auf die Unix-Zugriffsberechtigungsbits bezieht und Lese-, Schreib- und Ausführungsberechtigungen für die Welt, die Gruppe und den Besitzer bedeutet. Auf dem Mac und unter Windows werden andere Formen von Zugriffsberechtigungen für Dateien und Verzeichnisse verwendet, so dass Sie das Argument der Zugriffsberechtigung an `mkdir` hier eigentlich nicht brauchen (dennoch müssen Sie einen Wert übergeben, und deshalb ist `0777` eine gute, allgemein sinnvolle Wahl.)

Die `mkdir`-Funktion erzeugt das neue Verzeichnis in dem aktuellen Arbeitsverzeichnis des Skripts. Sie können das Verzeichnis aber auch überall sonstwo anlegen, indem Sie den vollen Pfadnamen angeben, zuerst das aktuelle Verzeichnis wechseln oder - falls Sie Spaß an ausgefallenem Code haben - die Möglichkeiten des Moduls `File::Path` nutzen, das Teil der Standardbibliothek ist und Funktionen zum Erstellen und Löschen ganzer Verzeichnisunterbereiche enthält.

Zum Entfernen eines Verzeichnisses gibt es den Befehl `rmdir`.

    rmdir temp;

Das Verzeichnis muss jedoch leer sein (das heißt es darf keine Dateien enthalten), damit Sie es löschen können.

## Ein Beispiel: Verknüpfungen erstellen

Jetzt möchte ich Ihnen ein richtig nützliches Beispiel präsentieren, das ich selbst recht häufig verwende: Wenn Sie ein Verzeichnis voller GIF- oder JPEG-Grafiken haben (in dem Verzeichnis also Dateien mit den Extension `.gif`, `.jpeg` oder `.jpg` stehen), können Sie mit Hilfe dieses Skripts eine HTML-Datei namens `index.html` erzeugen, die Verknüpfungen zu diesen Dateien enthält. Auf diesem Wege können Sie schnell eine große Menge an Grafikdateien in das Web stellen, ohne erst viel Zeit mit dem Anlegen der HTML-Datei zu verschwenden (wenn Sie möchten, können Sie die Datei selbstverständlich danach noch nachbessern).

Das Skript, das ich für diese Aufgabe geschrieben habe, verwendet Datei-Handles, um die Datei `index.html` zu öffnen und in die Datei zu schreiben, Verzeichnis-Handles, um den Inhalt des aktuellen Verzeichnisses einzulesen, und Datei-Tests mit regulären Ausdrücken, um die Dateien herauszufiltern, nach denen wir suchen. In Listing 17.1 sehen Sie das Ergebnis:

Listing 17.1: Das Skript imagegen.pl

    1:  #!/usr/bin/perl -w
    2:  use strict;
    3:  use Cwd;
    4:
    5:  open(OUT, ">index.html") or
                            die "Index-Datei kann nicht geöffnet werden: $!\n";
    6:  &printhead();
    7:  &processfiles();
    8:  &printtail();
    9:
    10: sub printhead {
    11:     my $curr = cwd();
    12:     print OUT "<HTML>\n<HEAD>\n";
    13:     print OUT "<TITLE>Bilddateien aus dem Verzeichnis $curr</TITLE>\n";
    14:     print OUT "</HEAD>\n<BODY>\n";
    15:     print OUT "<H1>Bilddateien</H1>\n";
    16:     print OUT "<P>";
    17: }
    18:
    19: sub processfiles {
    20:     opendir(CURRDIR, '.') or
                               die "Verzeichnis kann nicht geöffnet werden ($!),
            exiting.\n";
    21:     my $file = "";
    22:
    23:     while (defined($file = readdir(CURRDIR))) {
    24:         if (-f $file and $file =~ /(\.gif|\.jpe?g)$/i) {
    25:             print OUT  "<A HREF=\"$file\">$file</A><BR>\n";
    26:         }
    27:     }
    28:     closedir(CURRDIR)
    29: }
    30:
    31: sub printtail {
    32:     print OUT "</BODY></HTML>\n";
    33:     close(OUT);
    34: }

Wir beginnen, indem wir die Ausgabedatei (`index.html`) in Zeile 5 öffnen. Achten Sie auf das `>`-Zeichen, es zeigt Ihnen an, dass es sich hier um ein Handle für eine Ausgabedatei handelt.

Die Subroutine `&printhead()` in den Zeilen 10 bis 17 gibt lediglich den oberen Teil der HTML-Datei aus. Die einzige Schwierigkeit dabei besteht darin, das aktuelle Verzeichnis in den Titel der Seite aufzunehmen. Zu diesem Zweck wird die Funktion `cwd()` aufgerufen (die in Zeile 3 aus dem `Cwd`-Modul importiert wurde). Ich habe hier auf die Verwendung eines Hier-Dokuments für die Ausgabe des HTML-Codes verzichtet, denn es wären dann mit Sicherheit nur noch mehr Zeilen geworden, als es ohnehin schon sind.

In der Subroutine `&processfiles()` (Zeile 19 bis 29) findet die eigentliche Arbeit statt. Zeile 20 öffnet das aktuelle Verzeichnis (Mac-Anwender müssen das »`.`« in »`:`« ändern). Daran schließt sich in den Zeilen 23 bis 26 eine `while`-Schleife an, die jeden Dateinamen im Verzeichnis durchläuft. Der Test in Zeile 24 prüft, ob es sich bei der Datei auch tatsächlich um eine Datei und nicht um ein Verzeichnis handelt (der Test `- f`), und filtert mit Hilfe eines regulären Ausdrücks die Grafikdateien heraus (jene mit der Extension `.gif`, `.jpeg` und `.jpg`). Handelt es sich bei der aktuellen Datei wirklich um eine Grafikdatei, geben wir in Zeile 25 eine Verknüpfung zu dieser Datei aus, wobei wir den Dateinamen als Verknüpfungstext verwenden.

Beachten Sie, dass Sie durch die Verwendung der Dateinamen als Verknüpfungstexte nicht unbedingt zu sehr aussagekräftigen Verknüpfungen kommen - wenn Sie Pech haben, lauten Ihre Verknüpfungen »bild1.gif«, »bild2.gif«, »bild3.gif« und so weiter. Nicht sehr aussagestark, aber zumindest ein Anfang. Nachdem das Skript ausgeführt wurde, können Sie ja die HTML-Datei bearbeiten und aussagekräftigere Verknüpfungen erstellen (»netter Sonnenaufgang«, »Osterumzug«, »Bill verhält sich dumm« und so weiter).

Als Abschluß des Skripts rufen wir die Subroutine `&printtail()` auf, die das Ende der HTML-Datei ausgibt und den Ausgabedatei-Handle schließt.

## Vertiefung

Zusätzlich zu den Funktionen, die ich in dieser Lektion und in Kapitel 15 beschrieben habe und die allesamt in der perlfunc-Manpage beschrieben sind, gibt es in der Standard-Modulbibliothek noch eine Reihe weiterer Module mit Funktionen zum Verwalten von Dateien und Datei-Handles.

Viele dieser Module sind einfach objektorientierte Hüllen zu den Standard- Dateioperationen, andere wiederum sind besonders nützlich, um plattformübergreifende Probleme zu lösen oder zu umgehen. Wieder andere stellen einfach Funktionen zur Verfügung, die die Verwaltung von Dateien und Verzeichnissen leichter machen.

Einige dieser Module habe ich bereits erwähnt - beispielsweise `Getopt` und `Cwd`. In Tabelle 17.2 finden Sie eine ausführlichere Liste. Details zu den einzelnen Modulen finden Sie in der perlmod-Manpage.

Modulname | Beschreibung
--- | ---
`CWD` | Ermittelt das aktuelle Arbeitsverzeichnis auf sicherem, plattformübergreifendem Weg
`DirHandle` | Eine objektorientierte Version zum Manipulieren von Verzeichnis-Handles
`File::Basename` | Ermöglicht Ihnen, Datei- und Verzeichnis-Pfadnamen plattformübergreifend zu parsen
`File::CheckTree` | Führt mehrere Dateitests auf einer Gruppe von Dateien durch
`File::Copy` | Kopiert Dateien oder Datei-Handles
`File::Find` | Ähnlich dem Unix-Befehl `find`. Durchforstet einen Verzeichnisbaum nach einer speziellen Datei, die einem bestimmten Muster entspricht
`File::Path` | Erzeugt oder entfernt mehrere Verzeichnisse oder Verzeichnisbäume
`FileCache` | Auf einigen Systemen ist es nicht möglich, eine größere Anzahl von Dateien gleichzeitig geöffnet zu haben. Mit diesem Modul umgehen Sie dieses Problem
`FileHandle` | Eine objektorientierte Version zum Manipulieren von Datei-Handles
`Getopt::Long` | Verwaltet Skript-Argumente (komplizierte POSIXSyntax)
`Getopt::Std` | Verwaltet Skript-Argumente (einfachere Einzelzeichen-Syntax)
`SelectSaver` | Sichert Datei-Handles und stellt sie wieder her (wird zusammen mit der `select`-Funktion verwendet. Diese Funktion besprechen wir in Kapitel 20)

Tabelle 17.2: Dateibezogene Module

## Zusammenfassung

Ein Perl-Skript ist keine Insel. Es kommt der Punkt, da ist es sehr wahrscheinlich, dass Ihr Skript sich mit der äußeren Welt des Dateisystems beschäftigen muss, besonders dann, wenn Ihr Skript extensiv Dateien liest und in Dateien schreibt. In Kapitel 15 haben Sie gelernt, wie man den Inhalt einer Datei liest und in eine Datei schreibt. Heute haben wir uns den globaleren Themen der Datei- und Verzeichnisverwaltung innerhalb von Perl-Skripten gewidmet.

Begonnen haben wir mit den Dateien: wie man sie umbenennt, wie man für sie Verknüpfungen erstellt und wie man sie verschiebt - ganz so wie man mit Dateien von der Befehlszeile aus oder in einem grafischen Dateimanager arbeiten würde.

Wenn man Dateien verwalten kann, sollte man auch in der Lage sein, Verzeichnisse zu verwalten. In der zweiten Hälfte dieser Lektion ging es daher darum, wie man Verzeichnisse anlegt und löscht, das aktuelle Arbeitsverzeichnis ausgibt oder wechselt und wie man Listen von Dateien aus einem Verzeichnis einliest (entweder durch Datei-Globbing oder durch Lesen aus einem Verzeichnis-Handle).

## Fragen und Antworten

Frage:
Ich versuche, ein portierbares Skript zu schreiben, das eine `config`-Datei einliest. Ich kann in den Pfadnamen für Unix und Windows den Schrägstrich verwenden, aber der Mac mit seinen Doppelpunkten in den Pfadangaben macht die Sache doch sehr kompliziert. Wie kann ich das Problem lösen?

Antwort:
Sie können mit Hilfe eines Tests feststellen, ob Sie Ihr Skript auf einem Mac ausführen, und dann das Pfad-Trennzeichen ordnungsgemäß einrichten (und Ihren Dateipfad durch das Aneinanderhängen von Strings aufbauen). Verwenden Sie das `Config`-Modul, um herauszufinden, auf welcher Plattform Sie sich befinden:

        use Config;
    if ( $Config{'osname'} =~ /^macos/i ) {
       # ... Macintosh-spezifische Anweisungen
    }

Frage:
Ich arbeite auf einem Unix-Rechner. Ich habe ein Skript getestet, das den Inhalt einer Datei einliest und die Datei dann löscht. Da ich wollte, dass die Datei erst gelöscht wird, nachdem das Skript debuggt worden ist, habe ich für die Datei die Zugriffsberechtigung zum Schreiben gelöscht. Trotzdem hat Perl die Datei einfach gelöscht. Warum?

Antwort:
Die Zugriffsberechtigungen einer Datei legen lediglich fest, ob Sie den Inhalt der Datei lesen und schreiben können oder nicht. Ob die Datei umbenannt, verschoben oder geändert werden kann, hängt von den Zugriffsberechtigungen des betreffenden Verzeichnisses ab. Wenn Sie in Perl verhindern wollen, dass Dateien gelöscht werden, müssen Sie die Schreibberechtigung für das Verzeichnis löschen, in dem sich die Datei befindet. Oder - noch besser - kommentieren Sie einfach Ihren `unlink`-Befehl aus, bis Sie den Rest Ihres Skripts debuggt haben. Unter `http:// www.perl.com/CPAN-local/doc/FMTEYEWTK/file-dir-perms` finden Sie eine Seite, auf der dieses Problem detailliert beschrieben ist.

Frage:
Sie haben beschrieben, wie man eine Datei umbenennt, eine Verknüpfung zu einer Datei erstellt und Dateien entfernt. Wie jedoch kopiert man eine Datei?

Antwort:
Sie können dazu beide Dateien öffnen (die Datei, die kopiert werden soll, und die Datei, in die kopiert werden soll) und dann die Zeilen aus der einen Datei auslesen und in die andere ausgeben. Oder Sie nutzen die schrägen Anführungszeichen, um den Kopierbefehl des Systems aufzurufen (wie Sie noch in Kapitel 18 lernen werden). Oder Sie verwenden das Modul `File::Copy`, das Ihnen eine Kopierfunktion zur Verfügung stellt, mit der Sie Dateien oder Datei-Handles von einer Quelle in ein Ziel kopieren können.

## Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie zur Lektion des nächsten Tages übergehen.

### Quiz

1. Was ist der Unterschied zwischen einer harten und einer symbolischen Verknüpfung? Was passiert jeweils, wenn Sie die Datei löschen, für die die Verknüpfung erstellt wurde?
2. Was ist der Unterschied zwischen ``pwd`` und `cwd()`? Warum sollten Sie die eine Form der anderen vorziehen?
3. Was ist Datei-Globbing? Wozu ist es nützlich?
4. Was ist der Unterschied zwischen einem Datei-Globbing mit `<*>` und einer Liste von Dateien, die Sie mit `readdir()` erhalten?
5. Welche Unterschiede bestehen zwischen Datei-Handles und Verzeichnis-Handles?
6. Was bewirkt das Zugriffsberechtigungsargument für `mkdir`?

### Übungen

1. Schreiben Sie ein Skript, das eine Liste von Dateien aus dem aktuellen Verzeichnis einliest und diese in alphabetischer Reihenfolge ausgibt. Sind darunter Verzeichnisse, so geben Sie diese Namen mit einem anschließenden Schrägstrich (`/` ) aus.
2. FEHLERSUCHE: Das folgende Skript soll alle Dateien und Verzeichnisse aus dem aktuellen Verzeichnis entfernen. Doch das Skript versagt. Warum?

           while (<foo*>) {
          unlink $_;
       }

3. FEHLERSUCHE: Wo liegt hier der Fehler?

           #!/usr/bin/perl -w

       opendir(DIR, '.') or die "Verzeichnis kann nicht geöffnet werden ($!)\n";
       while (readdir(DIR)) {

         print "$_\n";
       }
       closedir(DIR);

4. Schreiben Sie ein Skript, das Ihnen ein Menü mit fünf Optionen anzeigt: eine Datei erzeugen, eine Datei umbenennen, eine Datei löschen, alle Dateien auflisten und verlassen. Für die Option, eine Datei anzulegen, fordern Sie den Benutzer auf, einen Dateinamen anzugeben. Legen Sie die neue Datei (darf leer sein) in dem aktuellen Verzeichnis an. Für die Optionen zum Umbenennen oder Löschen fordern Sie zuerst den alten Namen der Datei an, die umbenannt oder gelöscht werden soll. Soll die Datei umbenannt werden, fordern Sie den Benutzer darüber hinaus auf, einen neuen Namen anzugeben. Für die Option, alle Dateien aufzulis- ten, sollten Sie es sich einfach machen. VORSCHLAG: Kümmern Sie sich nicht um Verzeichnisse. Zusatzaufgabe: Führen Sie für alle Dateinamen eine Fehlerprüfung durch, um sicherzustellen, dass Sie nicht eine Datei erzeugen, die bereits existiert, beziehungsweise eine Datei löschen oder umbenennen, die nicht exis-tiert.

## Antworten

Hier die Antworten auf die Workshop-Fragen aus dem vorigen Abschnitt.

### Antworten zum Quiz

1. Harte Verknüpfungen bieten die Möglichkeit, für eine Datei mehrere Dateinamen vorzusehen. Wenn Sie eine Instanz des Dateinamens entfernen - sei es das Original oder eine Verknüpfung -, hat dies keine Auswirkungen auf die anderen harten Verknüpfungen. Die Datei wird nur dann endgültig gelöscht, wenn auch tatsächlich alle Verknüpfungen dazu gelöscht sind. Symbolische Verknüpfungen bieten ebenfalls die Möglichkeit, eine Datei unter mehreren Dateinamen zu führen, jedoch ist diesmal die Originaldatei getrennt von den Verknüpfungen. Wenn Sie die Verknüpfung entfernen, hat das keine Auswirkung auf die Originaldatei, entfernen Sie hingegen die Originaldatei, weisen die Verknüpfungen ins Leere.
2. Beachten Sie, dass nur Unix-Systeme zwischen harten und symbolischen Verknüpfungen unterscheiden. Es gibt aber auch Unix-Systeme, die keine symbolische Verknüpfungen kennen. Die Aliase des Mac's und die Verknüpfungen unter Windows sind analog den symbolischen Verknüpfungen.
3. Der Befehl ``pwd`` verwendet schräge Anführungszeichen, um den Unix-Befehl `pwd` aufzurufen. Während manche Perl-Versionen auf anderen Systemen diesen Aufruf ebenfalls korrekt verarbeiten (namentlich MacPerl), gilt dies bei weitem nicht für alle Perl-Versionen. Soll Ihr Skript problemlos portierbar sein, verwenden Sie das Modul `Cwd` und die Funktion `cwd()`. Denken Sie daran, dass Sie `Cwd` erst importieren müssen (`use Cwd`), bevor Sie es verwenden können.
4. Datei-Globbing ist eine Methode, um eine Liste von Dateien zu erhalten, die einem bestimmten Muster entsprechen. `*.pl` zum Beispiel ist ein Glob, der alle Dateien in einem Verzeichnis zurückliefert, die die Extension `.pl` aufweisen. Globs sind besonders nützlich, wenn man einen Satz von Dateien aufgreifen und auslesen möchte, ohne explizit das Verzeichnis-Handle zu öffnen, die Liste zu lesen und die Dateien auszusortieren, die dem Muster entsprechen.
5. `<*>` liefert alle Dateien in einem Verzeichnis, abgesehen von den versteckten Dateien, den Dateien, die mit `».«` starten, und den Verzeichnissen »`.«` und »..«. Verzeichnis-Handles liefern alle diese Dinge zurück.
6. Datei-Handles dienen dazu, den Inhalt von Dateien zu lesen und in Dateien zu schreiben (nicht zu vergessen das Schreiben auf den Bildschirm oder in eine Netzwerkverbindung oder ähnliches). Verzeichnis-Handles werden verwendet, um Listen von Dateien aus Verzeichnissen auszulesen. Datei-Handle und Verzeichnis- Handle verwenden ihre eigenen Variablen, so dass es keine Namenskonflikte zwischen ihnen gibt.
7. Das Argument zu `mkdir` legt fest, welche Zugriffsberechtigung das neue Verzeichnis haben soll. Unter Unix folgen diese Zugriffsberechtigungen den normalen Format für Lese-, Schreib- und Ausführungszugriff für Besitzer, Gruppe oder Welt. Für Mac oder Windows ist das Argument der Zugriffsberechtigung ohne Bedeutung, dennoch müssen Sie es mit angeben (verwenden Sie einfach `0777`, und Sie bekommen keine Schwierigkeiten).

### Antworten zu den Übungen

1. Hier ist eine Antwort:

           #!/usr/bin/perl -w
       use strict;
       use Cwd;

       my $curr = cwd();
       opendir(CURRDIR, $curr) or
                           die "Verzeichnis kann nicht geöffnet werden ($!)\n";

       my @files = readdir(CURRDIR);
       closedir(CURRDIR);

       foreach my $file (sort @files) {
           if (-d $file) {
               print "$file/\n";
           }
           else { print "$file\n"; }
       }

2. Die Funktion `unlink` ist nur für das Löschen von Dateien gedacht. Verzeichnisse werden mit `rmdir` gelöscht. Geht es darum, alle Dateien und Verzeichnisse zu entfernen, müssen Sie die Dateinamen einzeln daraufhin prüfen, ob es sich um eine Datei oder ein Verzeichnis handelt, und dann die entsprechende Funktion aufrufen.
3. Der Fehler liegt in dem Test für die `while`-Schleife. Fälschlicherweise wurde angenommen, dass `readdir` in einer `while`-Schleife den nächsten Verzeichniseintrag der Variablen `$_` zuweist - genauso wie dies beim Einlesen einer Eingabezeile von einem Datei-Handle der Fall ist. Dies stimmt jedoch nicht. Für `readdir` müssen Sie explizit den nächsten Verzeichniseintrag einer Variablen zuweisen:

           while (defined($in = readdir(DIR))) { ... }

4. Hier sehen Sie ein Beispiel. Achten Sie besonders auf die Subroutine `&getfilename()`, die die Fehlerprüfung durchführt, um sicherzustellen, dass die Datei, die angegeben wurde, auch existiert (zum Umbenennen oder Löschen einer Datei) oder gerade nicht existiert (zum Erzeugen einer neuen Datei).

           #!/usr/bin/perl -w
       use strict;

       my $choice = '';
       my @files = &getfiles();

       while ($choice !~ /q/i) {
           $choice = &printmenu();
         SWITCH: {
             $choice =~ /^1/ && do { &createfile(); last SWITCH; };
             $choice =~ /^2/ && do { &renamefile(); last SWITCH; };
             $choice =~ /^3/ && do { &deletefile(); last SWITCH; };
             $choice =~ /^4/ && do { &listfiles(); last SWITCH; };
             $choice =~ /^5/ && do { &printhist(); last SWITCH; };
         }
       }

       sub printmenu {
           my $in = "";
           print "Wählen Sie einen Befehl (or Q to quit): \n";
           print "1. Datei erzeugen\n";
           print "2. Datei umbenennen\n";
           print "3. Datei loeschen\n";
           print "4. Alle Dateien auflisten\n";
           while () {
               print "\nIhre Wahl --> ";
               chomp($in = <STDIN>);
               if ($in =~ /^[1234]$/ || $in =~ /^q$/i) {
                   return $in;
               } else {
                   print "Keine gueltige Wahl.  1-4 oder Q, bitte,\n";
               }
           }
       }

       sub createfile {
           my $file = &getfilename(1);

           if (open(FILE, ">$file")) {
               print "Datei $file wurde erzeugt.\n\n";
               @files = &getfiles();
               close(FILE);
           } else {
               warn "$file konnte nicht geoeffnet werden ($!).\n";
           }
       }

       sub renamefile {
           my $file = &getfilename();
           my $in = '';

           print "Geben Sie den neuen Dateinamen ein: ";
           chomp($in = <STDIN>);

           if (rename $file,$in) {
               print "Datei $file wurde in $in umbenannt.\n";
               @files = &getfiles();
           } else {
               warn "$file konnte nicht umbenannt werden ($!).\n";
           }
       }

       sub deletefile {
           my $file = &getfilename();

           if (unlink $file) {
               print "Datei $file wurde geloescht.\n";
               @files = &getfiles();
           } else {
               warn "$file konnte nicht geloescht werden ($!).\n";
           }
       }

       sub getfilename {
           my $in = '';

           # ohne Argumente aufrufen, um sicherzustellen, dass die
           # Datei existiert
           my $new = 0;

           # mit Argument aufrufen, um sicherzustellen, dass die
           # Datei nicht existiert
           if (@_) {
              $new = 1;
           }

           while (!$in) {
               print "Geben Sie einen Dateinamen ein (? fuer Liste): ";
               chomp($in = <STDIN>);
               if ($in eq '?') {
                   &listfiles();
                   $in = '';
               } elsif ((grep /^$in$/, @files) && $new) {
                   # Datei existiert, keine neue Datei, OK
                   # Datei existiert, neue Datei: Fehler
                   print "Datei ($in) existiert bereits.\n";
                   $in = '';
               } elsif ((!grep /^$in$/, @files) && !$new) {
                   # Datei existiert nicht, neue Datei, OK
                   # Datei existiert nicht, keine neue Datei: Fehler
                   print "Datei ($in) nicht gefunden.\n";
                   $in = '';
               }
           }

           return $in;
       }

       sub getfiles {
           my $in = '';
           opendir(CURRDIR, '.') or
                            die "Verzeichnis konnte nicht geoeffnet werden ($!),
           exiting.\n";
           @files = readdir(CURRDIR);
           closedir(CURRDIR);
           return @files;
       }

       sub listfiles {
           foreach my $file (@files) {
               if (-f $file) { print "$file\n"; }
           }
           print "\n";
       }

© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH