Wednesday, September 16, 2009

Verschiedenes zu "open"...

Der heutige Blog-Post ist Code geschuldet, den ich in letzter Zeit anschauen durfte. Dabei ist mir immer wieder die Verwendung von open aufgefallen. In der freien Wildbahn - und leider auch in einigen Perl-Tutorials im Internet - ist diese Form von open sehr häufig anzutreffen:

open( FH, $datei );

Diese Form hat aber ein paar Probleme:


Globale Filehandles

Globale Filehandles haben ein Problem: Sie sind global! In "Spaghetticode" ist das häufig kein Problem, aber sobald der Einstieg in Subroutinen da ist, wird es oft zum Problem. Gerade Perl-Einsteiger verwenden immer wieder den gleichen Namen für das Filehandle - auch weil in den Tutorials eigentlich durchgängig FH genommen wird.

Um das Problem mit globalen Filehandles zu verdeutlichen, nehmen wir den folgenden Code:


Hier wird eine Datei eingelesen, die verschiedene Dateinamen enthält, die auch wieder eingelesen werden sollen usw. In readFile() wird die Datei wie oben beschrieben geöffnet. Der Filehandle verweist auf die geöffnete Datei. Dann wird die erste Zeile eingelesen und diese Datei wird dann im nächsten Aufruf von readFile() geöffnet. FH ist jetzt ein Filehandle, das auf diese zweite Datei zeigt. Dann wird FH geschlossen und der Code geht zurück zum ersten Aufruf von readFile(). Perl soll jetzt die nächste Zeile auslesen. Mittlerweile ist FH aber geschlossen worden. Dadurch erscheint die Fehlermeldung

readline() on closed filehandle ...

In diesem Code-Beispiel ist der Fehler schnell ersichtlich, aber was ist wenn das Öffnen der Datei über den langen Code verteilt ist? Dann ist die Fehlersuche nicht wirklich ein Spaß.

Was lernen wir daraus? Lexikalische Filehandles nehmen:
open( my $fh, $datei );

Der Code ist immer noch nicht wirklich gut, also weiter...

Mehr zum Thema Lexikalische Filehandles gibt es auch im Wiki von Perl-Community.de.

Lesend? Schreibend?

Das nächste Problem beim ursprünglichen Code ist, dass Perl hier nicht explizit gesagt bekommt, ob die Datei lesend oder schreibend geöffnet werden soll. Perl impliziert hier, dass die Datei lesend geöffnet werden soll.

Dieser Code


gibt also einfach nur die erste Zeile aus test.txt aus. Soweit noch kein wirkliches Problem. Ein Problem wird das, wenn ein Benutzer nach einem Dateinamen gefragt wird und dann z.B. "> test.txt" eingibt, so dass sich das hier ergibt:


Das ausgeführt bringt die Fehlermeldung

Filehandle $fh opened only for output at ...

Was, wenn der Benutzer "> /etc/passwd" oder ähnliches eingibt? Darum sollte man immer explizit angeben, wie die Datei geöffnet wird.

open( my $fh, '<', $datei ); # lesend öffnen
open( my $fh, '>', $datei ); # schreibend öffnen

In Perls die älter als Version 5.6.0 sind, ist diese 3-Argumenten-Form nicht möglich. Dort sollte man das dann so schreiben:

open( my $fh, "< $datei" ); # lesend öffnen
open( my $fh, "> $datei" ); # schreibend öffnen

Die 3-Argumenten-Form hat aber noch einen weiteren Vorteil: Man kann Dateien mit führenden oder angehängten Leerzeichen im Namen öffnen. Die alte Form schneidet einfach alle Leerzeichen vor und hinter dem Dateinamen ab.

Fehlerbehandlung

Es sollte zum guten Ton in jedem Programm gehören, dass man Fehler abfängt. Der ursprüngliche Code hat das Problem, dass Perl einfach weitermacht, egal ob ein Problem auftaucht oder nicht. Was könnten hier Probleme sein? Die Datei könnte nicht existieren oder das Programm darf die Datei wegen fehlender Rechte nicht öffnen.

Ein einfacher Weg der Fehlerbehandlung ist ein einfaches "or die $!". In $! steckt die Fehlerbeschreibung (siehe auch perldoc perlvar).

Oder man kann das open in eine if-Bedingung stecken und dann im else-Teil (wenn ein Fehler auftritt) eine eigene Fehlerbehandlung machen - z.B. Logging der Fehler.

Eine einfache Methode, Fehler automatisch zu behandeln, ist die Verwendung von autodie. Wie das eingesetzt wird, wird in Ausgabe 9 vom Perl-Magazin $foo beschrieben.

Das open würde jetzt zum Schluss so aussehen:

open( my $fh, '<', $datei ) or die $!;

2 comments:

ReneeB said...

Der autodie-Artikel ist auch als Leseprobe vorhanden...

Anonymous said...

netter blog-eintrag. hat mir so manche fehler bei der benutzung von open() begreifbar gemacht.