Kapitel 13 - Datei Ein/Ausgabe

Datei Ein/Ausgabe

Was bringt einem die beste Programmiersprache, wenn man keine Daten Speichern oder wieder Laden kann. Bevor Sie nun aus einer Datei lesen oder schreiben, müssen Sie diese erst mal öffnen. Hierzu verwenden man das in C schon bekannte Schlüsselwort open. Hier ist der Syntax von open ...

    open(HANDLE,  "modus $DATEINAME");

Hiermit öffnen Sie eine Datei Namens $DATEINAME mit dem Modus modus. Mit dem erzeugten Datei-HANDLE können Sie anschließend die Datei weiterverarbeiten. Folgende Modi stehen Ihnen für das öffnen einer Datei zur Verfügung ...

  • open(HANDLE, "datei"); Datei zum Lesen öffnen
  • open(HANDLE, "< datei"); Datei zum Lesen öffnen, existiert diese nicht scheitert die Funkion
  • open(HANDLE, "+< datei"); Datei zum Lesen und Schreiben öffnen
  • open(HANDLE, "> datei"); Datei zum Schreiben öffnen, falls nicht vorhanden wird diese erzeugt
  • open(HANDLE, ">> datei"); Datei öffnen zum Anhängen, falls nicht vorhanden wird diese erzeugt

Sollte das öffnen einer Datei fehlschlagen, müssen Sie nicht wie in anderen Programmiersprachen den Rückgabewert der Funktion überprüfen sondern lediglich eine *or*-Verknüpfung mit der Funktion '*die*' machen ...

    open(HANDLE, "< $file")
        or die "\nKonnte Datei $file nicht zum Lesen öffnen\n";

Mit dem folgendem Skript können Sie aus einer Datei lesen ...

    #!/usr/bin/perl -w

    print "Welche Datei wollen Sie zum Lesen öffenen : " ;
    chomp ( $file=<>);

    open(FH, "< $file")
        or die "\nKonnte die Datei $file nicht zum Lesen öffnen\n";

    while($zeile=<FH>){
        if($. %10){     #Alle 10 Zeilen anhalten
            print $zeile;
        }
        else{
            print "Für weiteren Text bitte  drücken\n";
            <STDIN>;
        }
    }
    close(FH);

Mit diesem einfachen Beispiel öffnen Sie die Datei $file, falls vorhanden und geben den Text in Zehn-Zeilenweise aus. Wichtig ist übrigens auch das schließen eines Datei-Handle mit close. Dies versichert, dass eventuell sich noch im Puffer befindlichen Dateien geschrieben und Abgeschlossen werden.

Vorraussetzung das Sie Dateien öffnen können, ist übrigens auch der richtige Pfadnamen und der komplette Dateiname mit Endung.

Eine weitere Möglichkeit eine Datei zu öffnen ist die Kommandozeile ...

    #!/usr/bin/perl -w

    $counter=0;
    $file="null";
    foreach (@ARGV){
        $counter++;
        if($counter > 1){    #Wurde mehr als 1 Argument angegeben
            print "\nProgramm bitte nur mit 1 Argument starten\n";
            last;
        }
        $file=$_;
    }

    if($file eq "null" ){  #wurden überhaupt Argumente mit angegeben
        die "\nProgrammaufruf : programm file \n";
    }
    elsif($file eq "-h" or $file eq "-help"){
        die "\nSie haben den help-Schalter aufgerufen\n";
    }

    open(FH, "< $file")
        or die "\nKonnte die Datei $file nicht zum Lesen öffnen\n";

    while($zeile=<FH>){
        if($. %10){  #Alle 10 Zeilen anhalten
            print $zeile;
        }
        else{
            print "Für weiteren Text bitte drücken\n";
            <STDIN>;
        }
    }
    close(FH);

Hier haben Sie ein kleines Beispiel wie Sie die Kommandozeile mit der Standardvariablen @ARGV auswerten können.

Jetzt fehlt eigentlich nur noch die Möglichkeit, in einer Datei etwas zu Schreiben. Dabei wird es einem recht einfach gemacht in Perl ...

    #!/usr/bin/perl -w

    print "Welche Datei wollen Sie zum Anhängen öffnen : ";
    chomp( $file = <>);

    open(FH, ">> $file")
        or die "\nKonnte die Datei $file nicht zum Anhängen öffnen\n";

    print ">";

    $\ = "\n";           #Ausgabe mit mit \n beenden
    $zeile=<STDIN>;      #Eingabe von STDIN
    print FH $zeile;     #Eingabe in die geöffnete Datei $file schreiben

    close(FH);

Hier benutzen Sie lediglich die print-Anweisung und stellen nur den File-Handle davor. Dies funktioniert natürlich auch mit printf. Die default-Einstellung von print und printf lautet ja ...

    print STDOUT $variable;
    printf STDOUT "$variable";

Sie benutzen bisher immer die kürzere Schreibweise da es ja die default-Einstellung ist.

Somit sind Sie nun in der Lage, Dateien zu kopieren. Das folgend Programm dürfte nun kein Problem mehr für Sie sein ...

    #!/usr/bin/perl -w

    print "Welche Datei wollen Sie Kopieren : ";
    chomp ($quelle = <>);

    print "Wie soll die Kopie heißen : ";
    chomp( $ziel = <>);

    open(FHquelle, "< $quelle")
        or die "\nKonnte die Datei $quelle nicht zum Lesen öffen\n";

    open(FHziel, "> $ziel")
        or die "\n Konnte die Datei $ziel nicht zum Schreiben öffnen bzw. erzeugen\n";

    while($zeile = <FHquelle>){        #Lesen aus der Quelle
        print FHziel $zeile;           #Schreiben ins Ziel
    }
    
    close(FHquelle);
    close(FHziel);

Datei öffnen aus einem Programm

Eine weitere Interessante Möglichkeit zum Öffnen von Dateien, stellt das Öffnen und Lesen aus laufenden Programmen dar. Für open gelten in diesem Falls folgende Regeln ...

  • der Filehandle unterscheidet sich nicht zu den Beispielen vorher
  • der 2.Parameter ist der Formatstring mit dem Namen des zu startenden Programms. WICHTIG -> Am Ende des Formatstrings müssen Sie das Zeichen | einfügen ...

    open(FH, "less $datei |") or die ".....";
    open(FH, "ps -x |") or die "......";

Nun können Sie mit einem so geöffneten Programm, lesend auf die Ausgabe zurückgreifen. Das sich damit eine Unmenge an Flexibilität ergibt ist klar. Natürlich können Sie auch mehrere Programme mit Hilfe der Pipe auf einmal ausführen ...

    open(FH, "less $datei | cpio -o | gzip -9 |") or die "...";

Der Nachteil an dieser Methode ist, der Rückgabewert von open() kann nicht Zuverlässig überprüft werden, da popen() (Pipe) diese Methode ermöglicht.

Sie können als Beispiel die PID unseres eigenem Programms mit Hilfe von ps ausgeben lassen ...

    #!/usr/bin/perl -w
    #Linux/Unix only

    $pid = $$;

    open(FH, "ps -x |")
        or die "Kein solche Datei vorhanden...\n";

    while($tmp=<FH>){
        if($tmp =~ m/$pid/){
            print $0 , " : " , $tmp , "\n";
        }
    }

Langsam können Sie erkennen, wie mächtig aber simpel diese Methode ist. C-Programmierer unter Linux/Unix, kennen dies unter der Funktion popen(). Nun wird das Programm von oben verbessert und alle Prozesse die mit der Tastenkombination STRG+Z gestoppt wurden und alle Zombieprozesse, sollen gekillt (beendet) werden ...

    #!/usr/bin/perl -w
    #Linux/Unix only und geeignete Zugriffsrechte müssen vorhanden sein

    open(FH, "ps -x |")
        or die "Kein solche Datei vorhanden...\n";

    while($tmp=<FH>){
        if(($pid) =$tmp =~ m/(\d{4})/){ #Anpassen 4=Länge der PID!
            if($tmp =~ m/(\sT\s|\sZ\s)/){  #T=gestoppt Z=Zombie
                print $pid , " : " , $tmp , "\n";
                system("kill -9 $pid");  #killen
            }
        }
    }

Das Programm soll allerdings keine Schule machen, sondern dient nur zur Demonstration.

Datei öffnen zu einem Programm

Anders herum ist es in Perl ebenso möglich, mit open() Programme von der Standardeingabe zu füllen. Das Prinzip ist das selbe wie schon im Kapitel zuvor, nur das die Pipe (|) am Anfang des Strings steht ...

    open(FH, "| programmname > $outfile") or die ".......";

Dies eignet sich zum Beispiel hervorragend, um aus einem Programm heraus, mittels sendmail ein Mail zu verschicken ...

    #!/usr/bin/perl -w

    open(WHERE, "which sendmail |") or die "Kein sendmail gefunden ...\n";
    chomp($sendmail = <WHERE>);
    close(WHERE);

    $to = "root\@localhost.localdomain\n";  #Bitte anpassen
    open(MAIL, "| $sendmail -oi -t $to")
        or die "Can't open pipe to $sendmail: $!\n";
    print MAIL "From: Bill.Gates\@microsoft.com\n";
    print MAIL "Subject: Hallo Pinguin\n\n";
    print MAIL "Dies stellt den Inhalt der Mail da\n";
    close(MAIL) or die "Can't close pipe to $sendmail: $!\n";

    print "Die Mail wurde erfolgreich verschickt\n";

Und schon haben Sie eine Fake-Mail verschickt. Das Programm funktioniert natürlich nur wenn sich auf Ihrem Rechner auch das Programm sendmail befindet und dies entsprechend konfiguriert ist. Viele werden bei diesem Beispiel an die CGI-Programmierung denken. Und dabei haben Sie recht. Diese Beispiel werden Sie später in ähnlicher Weise bei der CGI-Programmierung, zum Verschicken einer Formmail, verwenden.

Häufig benötigte Rezepte

ABSATZWEISE

    #!/usr/bin/perl -w

    {
        local $/ = '';        #Absatzmodus
        while(<>){      #Absatz in $_
            print;
            print "\t\t\tABSATZ - weiter mit +\n";
            <STDIN>;
        }
    }

KOMPLETT EINLESEN

    #!/usr/bin/perl -w

    {
        local undef $/;        #Slurp mode
        while(<>){       #Kompletter Inhalt in $_
            print;
        }
    }

SATZWEISE

    #!/usr/bin/perl -w

    {
        local $/=".";  #Satztrennzeichen '.' -> Bitte Anpassen!
        while(<>){
            print;
            <STDIN>;     #Weiter mit STRG+D
        }
    }

Bei diesem Beispiel ist man besser mit einer Pattern-Matching-Operation beraten.

BESTIMMTE ZEILE

    #!/usr/bin/perl -w

    {
        local $/ = "\n";
        $suchzeilennr=50;
        while(<>){
            print $. ,":" , $_ if $. == $suchzeilennr;
        }
    }

ZEILENWEISE LESEN

    #!/usr/bin/perl -w

    {
        local $/ = "\n";
        while(<>){
            print $. ,":" , $_;
        }
    }

WORTWEISE LESEN

    #!/usr/bin/perl -w

    undef $/;                      #Slurp mode
    $_=<>;                   #alles in $_
    @worte=split(m/\s/, $_);       #Aufspliten durch Whitespace-Zeichen
    print "@worte";
    print "\n\nElemente im Array : " , $#worte+1 , "\n";

NACH DATEIENDUNGEN SUCHEN

    #!/usr/bin/perl -w

    @file = <*>;   #keine Files die mit einem '.' beginnen...
    @file2= <.*>;  #Alle versteckten Files die mit einem '.' beginnen...
    @file3= <*.c>; #Alle C-Files (z.B. test.c)
    @file4= <* .* *.c>; #Alle zusammen.....
    @file5= <*.txt>;     #alle Textdateien

    print join(' ', @file) , "\n\n";
    print join(' ', @file2) , "\n\n";
    print join(' ', @file3) , "\n\n";
    print join(' ', @file4) , "\n\n";
    print join(' ', @file5) , "\n\n";

DATEIENDUNG ÄNDERN

    #!/usr/bin/perl -w

    #Dateiendung verändern aus *.c wird *.c.bak
    foreach $rename(<*.c>){
        rename($rename, "$rename".".bak") or warn("Fehler bei rename....\n");
    }

DATEIENDUNG WIEDER RÜCKGÄNGIG MACHEN

    #!/usr/bin/perl -w

    #Dateiendung verändern aus *.c.bak wird *.c
    foreach $rename(<*.bak>){
        $temp=$rename;
        $temp=~s/\.bak$//;
        rename("$rename", "$temp");
    }

Filehandle als Rückgabewert

Um einen Filehandle aus einer Funktion zurückzugeben, müssen Sie einen Wrapper schreiben. Ein Wrapper ist ein Strumpf den man der Hauptfunktion überzieht. Folgendes Beispiel zeigt, wie Sie dies in Perl bewerkstelligen können ...

    #!/usr/bin/perl -w

    sub Open{
        my $pfad=$_[0];
        local *FH;
        open(FH, $pfad) or die $!;
        return *FH;
    }

    *FH_READ=Open("/usr/include/stdio.h");

    while(<FH_READ>){
        print;
    }

Hier wird die Wrapper-Funktion immer mit dem selben Namen verwendet. Nur den Anfangsbuchstaben wird dabei Großgeschrieben, um diese Funktion von der echten Funktion zu unterscheiden (a la Richard W. Stevens).

Wofür steht das Sternchen am Anfang des Filehandle? Testen Sie am besten folgendes Beispiel ...

    #!/usr/bin/perl -w

    *var1=5;
    *var2="hallo";

    print *var1 , ":" , *var2 , "\n";

Anhand der Aussage dürfte nun schon einiges klar sein. Das Sternchen ist nicht speziell für Filehandles reserviert, sondern kann bei allen möglichen Variablen verwendet werden. Also kann man sich das Sternchen als ein Art Wildcard für eine Variable vorstellen, die sowohl für $, @ und % steht. Aber auch für eine Filehandle, der kein Präfix besitzt.

Man kann es sich aber auch einfacher merken, wenn man sagt, das Sternchen ist das Präfix für einen Filehandle, auch wenn es nicht ganz richtig ist.

Bei der Übergabe als Funktionsparameter funktioniert das Ganze ähnlich ...

    #!/usr/bin/perl -w

    sub Read{
        local *FH=shift;
        while(<FH>){print;}
    }

    open(FH_READ,"/usr/include/stdio.h") or die $!;
    Read(*FH_READ);

sysopen, sysread und syswrite

sysopen und open unterscheiden sich Hauptsächlich dadurch, dass mit sysopen die Zugriffsrechte und der Öffnungsmodus genau festgelegt werden können. Hier ein Beispiel zu sysopen ...

    #!/usr/bin/perl -w

    use Fcntl;

    sysopen(FH, "./Neuedatei.txt", O_WRONLY|O_CREAT, 0600) or die $!;

    print FH "Dieser Text wird in Neuedatei.txt geschrieben\n";
    close(FH);

Anhand diese Beispiels können Sie erkennen, dass die Unterschiede zu open nicht allzu groß sind. Der Filehandle ist der selbe. Bei der Dateiangabe benötigen Sie nicht mehr die Zeichen <,>,+< usw. da Sie dies mit den O_*-Konstanten machen (welche das Modul Fcntl benötigen). Bei diesem Beispiel eben erzeugen Sie eine Datei (O_CREAT) ausschließlich zum Schreiben (O_WRONLY) mit den Zugriffsrechten 0600. Also nur für den Eigentümer. Also nochmals der Syntax zu sysopen ...

    sysopen(Filehandle,Dateiname,Öffnungsmodus,Zugriffsrechte);

Weitere Zugriffsarten zu sysopen lauten ...

Modus Bedeutung
O_RDONLY nur zum lesen öffnen (O_RDONLY=0)
O_WRONLY nur zum schreiben öffnen (O_WRONLY=1)
O_RDWR zum lesen und schreiben öffnen (O_RDWR=3)
O_CREAT Falls Datei nicht existiert wird Sie neu angelegt. Falls die Datei existiert wird ist O_CREAT ohne Wirkung.
O_APPEND Datei zu Schreiben am Ende "Anhängen".
O_EXCL O_EXCL kombiniert mit O_CREAT bedeutet das die Datei nicht geöffnet werden kann wenn Sie bereits existiert und open den Wert -1 zurückliefert. (-1 == Fehler)
O_TRUNC Eine Datei die zum Schreiben geöffnet wird wird geleert. Das Nachfolgende Schreiben bewirkt erneutes Beschreiben der Datei von Anfang an. Die Attribute der Datei bleiben erhalten.
O_NOCTTY Falls der Pfadname der Name eines Terminal ist, so sollte dies nicht der Kontrollterminal des Prozesses werden.
O_NONBLOCK Falls der Pfadname der Name einer FIFO oder Gerätedatei ist, wird diese beim Öffnen und bei nachfolgenden I/O- Operationen nicht blockiert.
O_SYNC Nach dem Schreiben mit write warten, daß der Schreibvorgang vollständig abgeschlossen ist.

Mit der Funktion sysread können Sie von einem Filehandle lesen. Außerdem liest sysread die Daten Ungepuffert ein. Auch hierzu wieder ein kurzes Skript ...

    #!/usr/bin/perl -w

    use Fcntl;

    sysopen(FH, "./Neuedatei.txt", O_RDONLY) or die $!;
    $bytes=sysread(FH, $buffer,50);
    print $buffer , "\n" if $bytes == 50;

    close(FH);

Nun die Erklärung zu ...

    $bytes=sysread(FH, $buffer,50);

Hiermit ließt man von der Datei mit dem Filehandle FH, 50 Bytes in den Puffer $buffer ein. Den Rückgabewert überprüfen Sie ebenso, ob auch wirklich 50 Zeichen an $buffer übergeben wurden.

Der Vorteil liegt auch hier wieder ganz klar bei der Performance. sysread ist bei größeren Datenmengen schneller als zum Beispiel read oder alle anderen gepufferten Einlesearten.

Das Gegenstück zu sysread ist syswrite. Auch hiermit können wir wieder ungepuffert in eine Datei schreiben. Die Anwendung von syswrite ist die selbe wie von sysread...

    #!/usr/bin/perl -w

    use Fcntl;

    sysopen(FH, "./Neuedatei.txt", O_RDONLY) or die $!;
    sysopen(BAK,"./backup.bak", O_WRONLY|O_CREAT, 0600) or die $!;

    $bytes_rd=sysread(FH, $buffer,50);
    $bytes_wr=syswrite(BAK, $buffer,50) if $bytes_rd == 50;
    print "Fehler beim Schreiben in backup.bak\n" if $bytes_wr != 50;

    close(FH);
    close(BAK);

Hier wird eine Kopie von Neuedatei.txt angelegt, die Sie zuvor mit sysread auslesen. Anschließend schreiben Sie die im $buffer ausgelesenen Zeichen, in den Filehandle BAK (wieder 50 Bytes).

Sie sollten bei den sys...-Aufrufen jedoch beachten, dass Sie diese nicht mit Funktionen der Standard-Bibliothek vermischen, wegen der Unterschiedlichen Pufferung. Mit Nicht-Vermischen sind die Funktionen sysread, syswrite, systell, sysseek gemeint. Diese sollten nicht mit read, seek, tell....usw. verwendet werden.

Funktion read

Mit read haben Sie die Möglichkeit, gepuffert von einem Filehandle einzulesen ...

    bytes_rd=read(Filehandle,Puffer, Anzahl Bytes);

Es werden damit also Anzahl Bytes Daten vom Filehandle in den Puffer eingelesen. Ein Programmbeispiel dazu ...

    #!/usr/bin/perl -w

    open(FH, "Neuedatei.txt") or die $!;
    open(BAK,"> backup.bak") or die $!;

    $bytes_rd=read(FH, $buffer,50);
    print BAK $buffer if $bytes_rd == 50;

    close(FH);
    close(BAK);

Der Vorteil von read gegenüber ...

    $buffer=<FH>;

... liegt daran, dass bestimmt werden kann wie viele Bytes in $buffer eingelesen werden können. Beispielsweise bei einem Eingabe-Formular des Namens bei einem CGI-Skript ...

    read(STDIN, $name, 80);

Hier kann der User so viele Zeichen wie er will eingeben. Gelesen werden dennoch nur 80 Zeichen.

Funktionen tell und seek

Mit der Funktion tell können Sie die aktuelle Position des Filehandle abfragen und mit seek können Sie die aktuelle Position ändern. Hier ein Demonstrationsprogramm dazu, was ausreichend kommentiert ist ...

    #!/usr/bin/perl -w

    use POSIX;

    open(FH, "Neuedatei.txt") or die $!;

    seek(FH, 5, SEEK_SET); #+5Bytes vom Anfang (SEEK_SET)
    $byte=tell FH; #akt. Position abfragen
    print "POS.: " , $byte , "\n";

    seek(FH, 6, SEEK_CUR); #+6Bytes von der aktuellen
    Position(SEEK_CUR) $byte=tell FH;
    print "POS.: " , $byte , "\n";

    seek(FH, 0, SEEK_END); #Dateiende (SEEK_END)
    $byte=tell FH;
    print "POS.: " , $byte , "\n";

    seek(FH, -20, SEEK_END); #Vom Dateiende 20 Bytes zurück
    (SEEK_END) $byte=tell FH;
    print "POS.: " , $byte , "\n";

    seek(FH, -15, SEEK_CUR); #Von der aktuellen Position 15 Bytes zurück
    $byte=tell FH;
    print "POS.: " , $byte , "\n";

    seek(FH, 0, SEEK_SET); #Zum Dateianfang
    $byte=tell FH;
    print "POS.: " , $byte , "\n";

    close(FH);

Der Report-Generator write

Um eine Ausgabe tabellarisch zu Gestallten, bietet sich der Report-Generator write an. Ich glaube jeder kennt das, zum Beispiel in C, die Ausgabe von printf. Testen ..., Passt nicht, ein Whitespace wegnehmen, wieder testen ..., passt wieder nicht ...u.s.w.

Zuerst müssen Sie dazu den Kopf- und Rumpfteil codieren. Dies geschieht mit dem Befehl format und endet mit einem '.' ...

    format STDOUT_TOP=
    .
    format STDOUT=
    .

Das Suffix _TOP bei STDOUT_TOP bedeutet das es sich hier um den Kopfteil handelt und ohne das Suffix _TOP haben wir den Rumpfteil. Hierzu ein Beispiel zur Demonstration ...

    #!/usr/bin/perl -w

    @array1=("Kurs1", "Kurs2", "Kurs3");
    @array2=(20.1, 33.2, 30.3);
    @array3=("+1.3", "-2,4", "-3.2");

    print "\n\n";

    for($i=0; $i<3; $i++){
        ($name, $stand, $plusminus)=($array1[$i],$array2[$i],$array3[$i]);
        write;
    }

    print "\n\n";


    format STDOUT_TOP=
    ****************************************************************
    *                                                              *
    *                    Kurstand vom 15.10.2001                   *
    *                                                              *
    ****************************************************************

    Kurs               Stand                Plusminus
    ------            -------              -----------
    .

    format STDOUT=
    @<<<<<<<<<             @<<<<<<<<                  @<<<<<<<<
    $name,$stand,$plusminus
    .

Und die Ausgabe lautet ...

    ****************************************************************
    *                                                              *
    *                    Kurstand vom 15.10.2001                   *
    *                                                              *
    ****************************************************************

    Kurs               Stand                Plusminus
    ------            -------              -----------
    Kurs1              20.1                   +1.3
    Kurs2              33.2                   -2,4
    Kurs3              30.3                   -3.2

Sie sehen es ist total simpel. Folgende Zeilen werden nun genauer erklärt.....

    #!/usr/bin/perl -w

    open(FH,"dat.csv") or die $!;
    while(<FH>){
        chomp;
        ($vorn,$name,$street,$plz,$ort,$alter)=(split(m/,/));
        write;
    }

    print "\n\n";


    format STDOUT_TOP=
    ****************************************************************
    *                                                              *
    *                  Adressdatenbank 15.10.2001                  *
    *                                                              *
    ****************************************************************

    Vorname    Name           Strasse           PLZ     Wohnort              Alter
    ------------------------------------------------------------------------------
    .

    format STDOUT=
    @<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<
    @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<
    @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<   @<<<<<<<<
    $vorn,$name,$street,$plz,$ort,$alter
    .

Dies ist der Rumpfteil oder Body wenn Sie wollen. Diese @<<<<< sind Platzhalter. Darunter befinden sich die Variablen. Es sind genau drei Platzhalter und drei Variablen zu finden, was bedeutet, das die Platzhalter durch die Variablen ersetzt werden. Folgende Platzhalter gibt es in Perl ...

  • @<<<<<<< - Linksbündig; @ plus Anzahl '<' geben die Länge des Ausgabenfeldes an
  • @>>>>>>> - Rechtsbündig; @ plus Anzahl '>' geben die Länge des Ausgabenfeldes an
  • @|||||| - Zentrierte Ausgabe; @ plus Anzahl '|' geben die Länge des Ausgabenfeldes an.
  • @###.### - Zahlenformat

Ausgelöst wird die ganze Ausgabe durch den Aufruf mit write.

Das Beispiel von soeben hat eigentlich nicht viel Sinn gemacht. Sinnvoller ist es schon, Beispielsweise eine CSV-Datei (dies ist eine Datei wo die einzelnen Elemente ein bestimmtes Trennzeichen haben, meist ein Komma oder Kommata) einzulesen ...

    Frank,Mustermann,Musterstrasse 10,88888,Musterhausen,33
    Albert,Scheizer,Kleinhausstrasse 5,12345,Augsburg,99
    Stefan,Wecker,Kohlstrasse 8,32343,Berlin,23

Diese Datei, nennen wir Sie dat.csv, lesen Sie nun mit folgendem Programm ein und geben diese sauber formatiert mit dem Report-Generator aus ...

    #!/usr/bin/perl -w

    open(FH,"dat.csv") or die $!;
    while(<FH>){
        chomp;
        ($vorn,$name,$street,$plz,$ort,$alter)=(split(m/,/));
        write;
    }

    print "\n\n";


    format STDOUT_TOP=

    Seite: @<<Name<   des Formats: @<<Name<<Kopfseite<<<<<<<<    @<<<<<<<<<<<<<<<<<<<<<<<<
    $%,$~,$^
    Zeilen pro Seite : @<<briggebliebenen<<<<            Ü : @<<<<<<<<<<<<<<<<<
    $=,$-

    Vorname    Name           Strasse           PLZ     Wohnort              Alter
    ------------------------------------------------------------------------------
    .

    format STDOUT=
    @<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    @<<<<<<<<<  @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<   @<<<<<
    $vorn,$name,$street,$plz,$ort,$alter
    .

Auch für den Report-Generator gibt es reservierte Variablen ...

  • $~ - Name des Formats
  • $^ - Name des Kopfformats
  • $% - Aktuelle Seite
  • $= - Anzahl Zeilen pro Seite
  • $- - Anzahl Zeile die noch Übrig sind pro Seite

Auch zu den reservierten Variablen folgt nun ein Beispielsskript ...

    #!/usr/bin/perl -w

    open(FH,"dat.csv") or die $!;
    while(<FH>){
        chomp;
        ($vorn,$name,$street,$plz,$ort,$alter)=(split(m/,/));
        write;
    }

    print "\n\n";


    format STDOUT_TOP=

    Seite: @<<Name<   des Formats: @<<Name<<Kopfseite<<<<<<<<    @<<<<<<<<<<<<<<<<<<<<<<<<
    $%,$~,$^
    Zeilen pro Seite : @<<briggebliebenen<<<<            Ü : @<<<<<<<<<<<<<<<<<
    $=,$-

    Vorname    Name           Strasse           PLZ     Wohnort              Alter
    ------------------------------------------------------------------------------
    .

    format STDOUT=
    @<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<
    @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    @<<<<<<<<<  @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<   @<<<<<
    $vorn,$name,$street,$plz,$ort,$alter
    .

HINWEIS: Wegen Darstellungsproblemen mussten einige Zeilen mit @<<<.... gebrochen werden!!!

Verzeichnisse : opendir, readdir und closedir

Mit den Funktionen opendir, readdir und closedir haben Sie die Möglichkeit Dateiverzeichnisse, Eintrag für Eintrag durchzulaufen.

Mit opendir können Sie ein Verzeichnis, wie eine normale Datei, öffnen. Ebenso wie bei einer normalen Datei haben Sie als ersten Parameter ein Handle, das Directory-Handle. Dies ist genauso einzusetzen wie schon das File-Handle. Als zweiten Parameter benötigen Sie die Pfadangabe, die Sie öffnen wollen. Wird keine Pfadangabe gemacht gilt das aktuelle Verzeichnis. Im Fehlerfall liefert opendir undef zurück. Auch hier steht der Fehler in der reservierten Fehler-Variablen $! Hier der Syntax zu opendir ...

    opendir(Directoryhandle, Pfad) or die $!;

Um nun ein Beispiel dafür zu zeigen, benötigen Sie noch die Kenntnis zu einer Funktion mit der Sie dieses Verzeichnis auslesen können, nämlich readdir. Mit readdir können Sie nun, bei Erfolgreichen öffnen mit opendir vorausgesetzt, das Verzeichnis auslesen. Hierzu der Syntax zu readdir ...

    while( defined ($name=readdir(Directoryhandle))){ .... }

Mit closedir geben Sie das nicht mehr benötigte Directory-Handle frei. Der Syntax zu closedir lautet ...

    closedir Directoryhandle;

Hierzu ein Skript, womit das aktuelle Verzeichnis ausgelesen wird ...

    #!/usr/bin/perl -w

    opendir(DH, ".") or die "Fehler bei opendir : $!\n";
    while(defined($name=readdir(DH))){
        print $name , "\n";
    }
    closedir DH;

Wenn Sie nun z.B. nach Textdateien in Ihrem Verzeichnis suchen, brauchen Sie nur den Pattern-Matching-Operator verwenden ...

    #!/usr/bin/perl -w

    opendir(DH, ".") or die "Fehler bei opendir : $!\n";
    while(defined($_=readdir(DH))){
        print $_ , "\n" if $_=~m/.*\.txt/;
    }
    closedir DH;

Wenn Sie nur Verzeichnisse Auslesen wollen, brauchen Sie nur den Schalter -d (Directory) verwenden ...

    #!/usr/bin/perl -w

    opendir(DH, ".") or die "Fehler bei opendir : $!\n";
    while(defined($_=readdir(DH))){
        print $_ , "\n" if -d;
    }
    closedir DH;

Mehr zu den Dateioperatoren, wie hier mit -d, erfahren Sie später.

Verzeichnisse erstellen und löschen

Ein Verzeichnis zu erstellen ist recht einfach. Dafür benötigen Sie lediglich die Funktion mkdir. Als Parameter für mkdir übergeben Sie, den Verzeichnisnamen mit, wenn es nicht im aktuellen Verzeichnis angelegt werden soll, Pfadangabe. Als zweiten Parameter übergeben Sie die Zugriffsrechte auf dieses Verzeichnis ...

    mkdir("newdir", 0700) or warn $!;

Mit der Funktion rmdir können Sie ein leeres Verzeichnis (nur bei Linux/Unix), es sind nur noch '.' und '..' in diesem Verzeichnis, löschen ...

    rmdir("newdir") or warn $!;

Da man mit rmdir nur eine leeres Verzeichnis löschen kann, steht man vor dem Problem, wie man den Inhalt diese Verzeichnisses löschen kann. In einer Konsole bei Linux könnten Sie folgt vorgehen ...

    rm -rf newdir

Also könnten Sie dies unter Perl ebenso verwenden ...

    system("rm -rf $verzeichnis") == 0 or warn "Fehler";

Unter Unix/Linux mag das funktionieren, aber da es Perl auch noch auf anderen Systemen gibt empfiehlt es sich das Standardmodul File::Path anzusehen. Hier nun das Beispiel ...

    #!/usr/bin/perl -w

    use File::Path;

    mkdir("mydir", 0700)
        or die "Fehler bei mkdir.... $!\n";
    system("touch ./mydir/a1.txt ./mydir/a2.txt ./mydir/a3.txt") == 0
        or die $! , "\n";

    #Verzeichnis auslesen
    opendir(DH, "./mydir") or die "Fehler bei opendir : $!\n";
    while(defined($_=readdir(DH))){
        print $_ , "\n";
    }
    closedir DH;

    rmdir("./mydir") or warn "Kann Verzeichnis ./mydir nicht löschen : $! \n";

    print "Neuer Versuch.........\n";
    rmtree("./mydir") or die "Konnte ./mydir nicht löschen\n";
    print "........Erfolgreich\n";

Hier verwenden Sie die Portable Version mit dem Modul File::Path und die darin enthaltene Funktion rmtree.

Dateioperatoren und Attribute mit stat

In Perl gibt es einige Dateioperatoren, mit denen Sie Informationen verschiedenster Art zu einer Datei bekommen. Ich werde dabei nicht auf alle Eingehen. Zuerst folgende Dateitestoperatoren ...

  • -e existiert die Datei?
  • -z ist die Datei 0 Bytes groß (Zero)
  • -s Länge in Bytes

    #!/usr/bin/perl -w

    $file="file.txt";

    #Falls file.txt nicht existiert (-e) wird Sie erzeugt....
    system("touch file.txt") if not -e $file;
    #Überprüfung ob file.txt existiert.....
    print "Datei file.txt exisitiert\n" if -e $file;

    #Wenn Datei 0Bytes gross ist öffnen....
    open(FH,">file.txt") if -z $file;
    #Mit 100 Bytes füllen
    print FH "x" x99 , "\n";
    close FH;

    print "Datei hat nicht die Länge Null\n" if not -z $file;

    #Wir überprüfen die Grösse in Bytes von file.txt
    print "Datei ist grösser als 100 Bytes\n" if -s $file > 100;
    print "Datei ist 100 Bytes gross\n" if -s $file == 100;
    #So gehts auch......
    print -s $file , " Bytes\n";

    #Wieder weg damit.....
    unlink($file);

Das Programm erzählt die Anwendungen der Dateitestoperatoren von selbst.

    #!/usr/bin/perl -w

    opendir(DH, ".") or die "opendir $!\n";
    while(defined($name=readdir(DH))){
       print $name , "\n" if -s $name > 1024*10;
    }
    closedir DH;

Mit diesem Beispiel geben Sie zum Beispiel alle Dateien aus, die Größer als 10KB sind. Idealer lässt sich der -s Operator mit grep verwenden ...

    #!/usr/bin/perl -w

    @files = <*.txt>;

    @number = grep { -s > 1024*10 }@files;

    print "Textdateien grösser als 10KB : \n";
    foreach(@number){
        print $_ , "\n";
    }

Hier werden alle Textdateien ausgegeben die größer als 10KB sind.

Um zu testen um was für eine Dateiart es sich handelt, gibt es folgende Dateitestoperatoren ...

  • -d Directory
  • -f normale Datei
  • -p Pipe
  • -l Symlink
  • -S Socket
  • -b Gerätedatei Blockweise
  • -c Gerätedatei Zeichenweise
  • -t Terminal (TTY)

Eingesetzt werden diese Dateioperatoren genauso wie schon oben gesehen. Auch hierzu ein Beispiel ...

    #!/usr/bin/perl -w

    @files = <*>;

    @directory = grep { -d }@files;
    @file = grep { -f }@files;

    print "Alle VERZEICHNISSE : \n";
    print "@directory\n\n";
    print "Alle regulären DATEIEN : \n";
    print "@file\n\n";

Hier kommen alle Dateiverzeichnisse in @directory und alle regulären Dateien in @file.

Wenn Sie noch mehr Informationen über eine Datei oder offenes File-Handles benötigen, können Sie auch die Funktion stat verwenden. stat liefert bei Erfolg eine Liste mit 13 Elementen zurück mit folgendem Inhalt, in der selben Reihenfolge ...

  • dev Gerätenummer des Dateisystems
  • ino Inode-Nummer
  • mode Zugriffsrecht und Dateityp
  • nlink Zähler Harter Links
  • uid User-ID
  • gid Group-ID
  • rdev Raw-Gerätenummer des Dateisytems
  • size Logische Größe in Bytes
  • atime Letzte Zugriffszeit
  • mtime Letzte Veränderung
  • ctime Letzte Inode-Veränderung
  • blksize Ideale Blockgröße für Datei-I/O
  • blocks Anzahl zugewisener Blöcke

Und auch hierzu das Anwendungsbeispiel zu stat ...

    #!/usr/bin/perl -w

    $file="out.txt";
    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
     $atime,$mtime,$ctime,$blksize,$blocks) = stat($file);

    $at=scalar localtime($atime);
    $mt=scalar localtime($mtime);
    $ct=scalar localtime($ctime);

    print "Datei : $file\n";
    print "Device : ($dev, $rdev) Inode : $ino\n";
    print "Mode : $mode\n";
    print "Links : $nlink\n";
    print "Eigentümer : ($uid, $gid)\n";
    print "Grösse(logisch) : $size Bytes\n";
    print "Grösse(physisch) : $blocks\n";
    print "Blockgrösse : $blksize\n";
    print "Letzter Zugriff : $at\n";
    print "Letzte Änderung : $mt\n";
    print "Inode-Änderung : $ct\n";

Dateien löschen

Um ein Datei zu löschen benötigen Sie die Funktion unlink. Mit unlink löschen Sie den Dateinamen. Beispielsweise ...

    unlink($name) or die $!;

Die, in anderen Programmiersprachen, bekannten Löschfunktionen wie remove oder delete gibt es in Perl nicht. Und um genau zu sein Löschen Sie mit unlink nicht die Datei von der Festplatte, sondern es wird nur der Name aus dem Dateiverzeichnis entfernt.

Recht praktisch ist unlink auch, weil Sie damit eine ganze Liste von Dateinamen löschen können ...

    unlink(@kill_all) or die $!;

Auf Unix-Systemen können Sie auch folgende Version zum löschen von Dateinamen verwenden ...

    system("rm $name");
    system("rm", @files);

Auf anderen Systemen, wo rm nicht vorhanden ist, wird dies Programm aber dann nicht laufen.

Dateien sperren

Wozu benötigen Sie Dateisperren? Wenn mehrere Prozesse auf eine Datei zugreifen und in diese Datei schreiben wollen. Zum bei CGI-Skripts wie ein Gästebuch oder ein Besucher-Counter. Zwei Besucher auf Ihrer Seite tragen sich gleichzeitig ins Gästebuch ein und wollen zur gleichen Zeit in die selbe Datei schreiben. Die Folge wäre ein Datensalat. Als Abhilfe dafür benutzt man dazu Dateisperren.

Wie kann man nun ein ganze Datei sperren? Hierzu ein simples Skriptbeispiel ...

    #!/usr/bin/perl -w

    use Fcntl qw(:DEFAULT :flock);

    open(FH, "<<test.txt") or die "open : $!\n";

    flock(FH, LOCK_EX) or die "Fehler flock : $!\n";

    #Ab hier kann kein anderer Prozess mehr auf die Datei zugreifen
    print FH "Dieser Text kann nur von PID $$ geschrieben werden\n";

    flock(FH, LOCK_UN) or die "Fehler flock $!\n";
    #Jetzt können wieder andere Prozesse auf test.txt zugreifen

    close FH;

Die erste Frage dürfte nun lauten, was passiert wenn User1 in die Datei schreibt und 1 Sekunde darauf User2 und der Schreibvorgang von User1 war noch nicht fertig? Es passiert gar nichts. User2 wird solange blockiert bis User1 die Dateisperre wieder freigibt.

Zwei Arten von Sperren können Sie verteilen. In unserem Beispiel haben Sie mit LOCK_EX eine exklusive Sperre verwendet. Mit dieser Sperre stellen Sie sicher, das niemand anderes während dieser Zeit diese Datei lesen noch beschreiben kann.

Die zweite Möglichkeit währe eine gemeinsame Sperre (LOCK_SH). Mit dieser dieser Sperre können Sie realisieren das ein Prozess eine Exklusiv-Sperre erhält, damit der Prozess mit der Exklusiv-Sperre, die Lesenden Prozesse nicht stört. Das daher weil alle Prozesse ein Sperre erhalten haben (shared lock).

Sperren sind übrigens nicht mit Zugriffsrechte gleichzusetzen. Es sorgt immer wieder für Verwirrung, dass man trotz einer Sperre die Datei lesen kann. Die Sperre gilt nur für Prozesse die ebenfalls mit flock, versuchen darauf zurückzugreifen. Bei CGI-Skripts brauchen Sie sich somit keine Gedanken darum machen. Es sei denn, jemand weiß wo sich das zu beschriebene File befindet und Sie haben die Zugriffsrechte falsch gesetzt.

Zurück zu Kapitel 12 | Zur Übersicht | Weiter zu Kapitel 14

-- EnricoEhrhardt - 22 Aug 2008
Topic revision: r4 - 2008-09-02 - 16:07:00 - EnricoEhrhardt
 
Bitte die NutzungsBedingungen beachten.
Bei Vorschlägen, Anfragen oder Problemen mit dem PerlCommunityWiki bitten wir um WebBottomBarExample">Rückmeldung.