Perl 1

Perl 2

Perl 3

Perl 4

Perl 5

Perl 6

Zurück

Einführung in Perl - Teil V

Dateibehandlung

von Achim Schmidt


Während wir uns im letzten Teil dieser Serie Felder in Perl etwas näher angesehen haben, kommen wir heute zu einem weiteren Kernstück, nämlich der Dateibehandlung. Mit Perl ist es relativ einfach möglich, auf Dateien zuzugreifen und diese durch eigene Funktionen auszuwerten oder zu modifizieren.

Filehandles

Grundsätzlich wird in Perl jede Datei über ein sogenanntes Filehandle angesprochen, welches beim Öffnen einer Datei definiert wird. Genaugenommen arbeiten wir auch schon einige Zeit mit Filehandles, ohne daß dies direkt aufgefallen ist. Unser STDIN ist nichts anders als ein solches Handle für die Standardeingabe. Analog dazu existieren noch STDOUT für die Standardausgabe und STDERR für die Standardfehlerausgabe. Wollen wir nun mit eigenen Dateien arbeiten, muß zunächst eine Verbindung zwischen Filehandle und Datei hergestellt werden. Eben diese Verbindung stellt die open () Anweisung her:
	open ( HANDLE, "Dateiname");
verbindet die Datei Dateiname mit den Hanlde HANDLE. Der Name des Filehandle ist grundsätzlich frei wählbar und benutzt, wie wir es bereits gewohnt sind einen anderen Namensraum als unsere Variablen und Feldbezeichner. Nomalerweise werden Filehandles, auch in Hinsicht auf neue reservierte Wörter, groß geschrieben.

Nun müßen wir Perl beim Anlegen eines solchen Handles nur noch sagen, ob wir die Datei zum Lesen, Schreiben oder Anhängen öffnen wollen. Hier kommen nun auch die von der Shell her bekannten Operatoren <, > und >> ins Spiel. Diese werden einfach dem Dateinamen vorangestellt. Somit weiß das Perlprogramm in welchem Modus die Datei zu öffnen ist:

	Operator	Beispiel			Funktion
	<		open ( EIGDAT, "< test");	öffnet die Datei test zum Lesen
	>		open ( EIGDAT, "> test");	öffnet die Datei test zum Schreiben
	>>		open ( EIGDAT, ">> test");	öffnet die Datei test zum Anhängen
Mittels des zugewiesenen Filehandles wird die geöffnete Datei nun im Verlauf des Programmes angesprochen, aber das sehen wir uns am besten anhand eines kleinen Beispieles an:
Programm: demo5-1.pl

#!/usr/bin/perl

open (TESTDAT, "< test");
while ($zeile=<TESTDAT>) {
  print $zeile; 
}
close (TESTDAT);
Dieses kleine Programm öffnet die Datei test, liest es innerhalb der Schleife Zeile für Zeile in die Variable $zeile ein und gibt diese anschließend aus. Am Ende der Datei liefert der Zugriff auf das Filehandle false, wodurch die Schleife beendet wird. Danach wird die Datei mittels der close () Anweisung wieder geschloßen.

Fehler beim Öffnen einer Datei

Wird nun eine nicht existente Datei geöffnet, liefert die open () Anweisung zum einen ein false, zum anderen wird auch der Zugriff innerhalb des Schleifenkopfes direkt ein false liefern, wodurch die Schleife erst gar nicht durchlaufen wird. Auf den gesammten Programmablauf hat dies jedoch keinen weiteren Einfluß. Da es jedoch oftmals nicht unwesentlich ist, ob eine Datei geöffnet wurde oder nicht, ist es oftmals sinnvoll, beim Fehlschlagen einer solchen Operation das Programm mittels eines entsprechenden Hinweises abzubrechen. Auch dies ist sehr simpel zu implementieren. Einen Programmabbruch kann man mittels des die () Befehles erzeugen, welcher einen optionalen Text mitausgibt und außerdem den Programmnamen und die Zeilennummer in welcher der Abbruch generiert wurde anzeigt.

Die einfachste Form, die die () Anweisung zu verwenden, sehen Sie in Listing demo5-2.pl, in welchem die Prüfung auf erfolgreiche Dateiöffnung einfach mittels des || (oder) Operators gemacht wird.

Programm: demo5-2.pl


#!/usr/bin/perl

open (TESTDAT, "< test") || die "Fehler beim Öffnen von test" ;
while ($zeile=<TESTDAT>) {
  print $zeile; 
}
close (TESTDAT);
Sehen wir uns nun das Ganze doch einmal an einem etwas sinnvolleren Beispiel an. Das Programmpaket netacct von Ulrich Callmeider (uc@brian.lunetix.de), welches den Netzverkehr "accountet", erstellt eine Logdatei mit folgender Syntax:
786476591   6   193.98.158.1   119   192.76.152.9   2072   5370   eth0  unknown
Dabei sind:
  1. Zeit in Sekunden seit 1.1.1970
  2. Protokoll
  3. Source IP
  4. Source Port
  5. Destination IP
  6. Destination Port
  7. Datengröße
  8. Interface über welches die Daten liefen
  9. Infofeld bei PPP / SlIP
Das Beispiel demo5-3.pl wertet diese Datei nun aus, indem eben eine solche Logdatei nach einer speziellen IP Adresse durchsucht wird und der entsprechende Traffic (gesendet und Empfangen) addiert und zum Schluß ausgegeben wird.
Programm: demo5-3.pl

#!/usr/bin/perl
####################################################
##
## NetAcct Auswertungsprogramm
## as@Saar.DE
##

#Programmnamen erkennen
@cmd = split ('/', $0);
$aufruf = $argv0 = @cmd[$#cmd];

# Aufrufparameter checken 
if (@ARGV[1]) { # Wenn Parameter da, Auswertung starten
  $logdatei = @ARGV[0];
  $ipaddress = @ARGV[1];

  # Logdatei öffnen
  open ( LOGDAT, "< $logdatei") || die "Fehler beim Öffnen von $logdatei" ;
  $traffic = 0;
  while (chop($zeile = <LOGDAT>)) { # Datei zeilenweise durchgehen
	@acctinfo = split ('\t', $zeile); # Zeile in Array splitten
	if (($acctinfo[2] eq $ipaddress) || ($acctinfo[4] eq $ipaddress)) {
	  $traffic += $acctinfo[6]; # Traffic accounten wenn IP Adresse stimmt
	}	# End-of-IF
  }	# End-of-WHILE
  print "Traffic von $ipaddress in $logdatei: $traffic bytes\n";
  close (LOGDAT); # Datei schließen

} else { # Wenn Parameter fehlen, usage - Meldung ausgeben

  print "usage: $aufruf <Netacct-Logdatei> <IP-Adress to account>\n\n";

}

Das Programm geht zunächst hin und merkt sich den Namen, unter welchem es aufgerufen wurde, um ggf. eine korrekte Usagemeldung auszugeben. Anschließend wird geprüft, ob die beiden benötigten Parameter (Name der zu verwendenden Logdatei und der zu accountenden IP Adresse) existieren. Dies ist mittels des Arrays @ARGV möglich ist. Dies enthält (natürlich bei 0 beginnend) einen einzelnen Aufrufparameter und stellt Sie dem Perlprogramm bereit. Existieren also zwei Übergabeparameter startet der eigentliche Programmlauf, anderenfalls wird die Usagemeldung im else Zweig der großen If-Schleife ausgeführt. Im weiteren Programmverlauf wird nun als nächstes die Logdatei zum Lesen geöffnet und die eingelesene Zeile in ein Array aufgeteilt (mittels split ()). Aufgeteilt wird nach Tabulatorzeichen, nachdem dies die Feldtrenner der Logdatei sind, bietet es sich wohl an :-).

Als weiteres wird nun mittels der IF Anweisung geprüft ob der dritte ($acctinfo[2]) oder der fünfte ($acctinfo[4]) Skalar des Arrays (also Quell- oder Ziel Adresse) mit der zu accountenden IP Adresse übereinstimmen. Ist dies der Fall, wird die entsprechende Datenmenge zu dem Trafficzähler ($$traffic += $acctinfo[6]) summiert. Sollte weder Quell- noch Ziel-IP mit der zu accountenden Adresse übereinstimmen, wird schlicht mit dem nächsten Eintrag der Logdatei weiterverfahren, ohne eine Summierung vorzunehmen. Wurde nun jede Zeile der Logdatei bearbeitet, wird eine Meldung mit der errechneten Trafficmenge auf dem Bildschirm ausgegeben und die Datei wieder geschloßen.

Somit ist es möglich mit einem simplen Perlskript Übersicht über den Trafficanfall im Lan oder Wan zu wahren.

Zugegeben, will man nun mehrere IP Adressen mit accouten, ist es recht umständlich das Programm X-mal mit jeweils einer anderen IP Adresse zu starten. Nun auch diesem Problem kann recht einfach Abhilfe geschaffen werden. Das Beispielprogramm demo5-4.pl ist eine leicht modifizierte Version des Programmes demo5-3.pl. Diese Version verwendet keine einzelne IP-Adresse, sondern eine weitere Datei, welche die zu accountenden IP Adressen enthällt. Der Aufbau der Datei ist dabei recht simpel gehalten. In jeder Zeile dieser Steuerdatei steht eine zu accountende IP-Adresse, welche das Programm nun sequentiell abarbeitet.

Programm: demo5-4.pl


#!/usr/bin/perl
####################################################
##
## NetAcct Auswertungsprogramm
## as@Saar.DE
##

#Programmnamen erkennen
@cmd = split ('/', $0);
$aufruf = $argv0 = @cmd[$#cmd];

# Aufrufparameter checken 
if (@ARGV[1]) { # Wenn Parameter da, Auswertung starten
  $logdatei = @ARGV[0];
  $iplist = @ARGV[1];

  # Steuerdatei öffnen
  open ( IPLIST, "< $iplist") || die "Fehler beim Öffnen von $logdatei" ;
  while (chop ($ipaddress = <IPLIST>)) {
	# Logdatei öffnen
    open ( LOGDAT, "< $logdatei") || die "Fehler beim Öffnen von $logdatei" ;
    $traffic = 0;
     while (chop($zeile = <LOGDAT>)) { # Datei zeilenweise durchgehen
  	  @acctinfo = split ('\t', $zeile); # Zeile in Array splitten
	  if (($acctinfo[2] eq $ipaddress) || ($acctinfo[4] eq $ipaddress)) {
	    $traffic += $acctinfo[6]; # Traffic accounten wenn IP Adresse stimmt
	  }	# End-of-IF
    }	# End-of-LOGDAT-WHILE
    print "$ipaddress:\t$traffic bytes\n";
    close (LOGDAT); # Datei schließen
  } #End-of-IPLIST-While
  close (IPLIST) ; 

} else { # Wenn Parameter fehlen, usage - Meldung ausgeben

  print "usage: $aufruf <Netacct-Logdatei> <IP-Adr. Listendatei>\n\n";

}
Wie Sie bestimmt schon bemerkt haben, wird nun der eigentliche Programmteil des Programmes demo5-3.pl von einer weiteren while-Schleife umgeben. Zuvor wird jedoch die Steuerdatei, welche die zu accountenden IP-Adressen enthällt geöffnet. In der while-Schleife wird nun jeweile eine IP-Adresse aus der Steuerdatei eingelesen, mit welcher der Accountinglauf gestartet wird. Dieser Vorgang wird wiederholt, bis die äußere Schleife ein false liefert, sprich alle IP-Adressen der Steuerdatei abgearbeitet sind.

In Dateien schreiben

Nachdem Sie nun gesehen haben, wie man Dateien auslesen und bearbeiten kann, ist es natürlich auch interessant, wie man selbst in Dateien schreiben kann. Nun, auch dies gestaltet sich äußerst einfach. Zunächst muß die entsprechende Datei zum Schreiben geöffnet werden. Wie schon erwähnt geht dies über die Operatoren > (zum Neuanlegen) oder >> (zum Anhängen), welche innerhalb der open-Anweisung dem Dateinamen vorangestellt werden. Um nun in eine so geöffnete Datei zu schreiben, bedient man sich wie gewohnt der print Anweisung, hier jedoch mit Angabe des Filehandles der Datei in welche die gewünschten Informationen zu schreiben sind.

Sehen wir uns zunächst wieder ein einfaches Beispiel an, in welchem eine Zeile von der Tastatur eingelesen und anschließend in eine Datei test1 geschrieben wird, hier im append-, also anhängenden Modus.

Programm: demo5-5.pl

#!/usr/bin/perl

 print "Eingabezeile: "; chop ($eingabe=<STDIN>);
 open ( TDAT, ">> test1") || die "Fehler beim Öffnen von test1" ;
 print TDAT "$eingabe\n";
 close (TDAT);
Dieses kleine Programm liest nun eine Zeile ein und schreibt diese in die zuvor geöffete Datei.

Natürlich lassen sich mit dieser Möglichkeit auch etwas sinnvollere Programme realisieren. Sehen wir dazu doch mal das nächste Beispielprogramm an, welches einen keinen Textkonverter darstellt und aus normalen ASCII Dateien simple HTML Dateien macht, indem es einen HTML-Kopf um die Datei schreibt und die Umlaute und (einen Teil) der Sonderzeichen, wie beispielsweise das Gößer- und Kleinerzeichen in HTML konforme Schreibweise umwandelt. Dabei bleibt die Orginaldatei (ASCII) erhalten und es wird eine neue Datei mit HTML-Code erzeugt.

Programm: demo5-6.pl

#!/usr/bin/perl 

{
	print "Originaldatei: "; chop ($indat = <STDIN>);
	print "Neue Datei:    "; chop ($outdat = <STDIN>);
	print "HTML - Dokumenttitel:"; chop ($titel = <STDIN>);
	
	# Quelldatei öffnen
	open (INDAT,$indat) || die "Fehler beim Öffnen von $indat" ; 
	  # Zieldatei öffnen
  	  open (OUTDAT,">  $outdat") || die "Fehler beim Öffnen von $outdat";
        # HTML Kopf schreiben
	    print OUTDAT "<HTML>\n" ;
	    print OUTDAT "<TITLE>$titel</TITLE>\n<BODY>\n";
	    while ( <INDAT> ) { # Quelldatei einlesen und via Regexp ändern
	  	  s/&/&amp;/g;
		  s/>/&gt;/g ; 
		  s/</&lt;/g ;
		  s/\n/<BR>\n/g ;
		  s/\344/&auml;/g;
		  s/\366/&ouml;/g;
		  s/\374/&uuml;/g;
		  s/\337/&szlig;/g;
		  s/\304/&Auml;/g;
		  s/\326/&Ouml;/g;
		  s/\334/&Uuml;/g;
	      print OUTDAT ;
	    }
		# HTML Fuß schreiben
	    print OUTDAT "<P><HR>\n";
	    print OUTDAT "converted by ascii2html.pl\n";
	    print OUTDAT "</BODY>\n</HTML>\n";
	  close (INDAT); # Quelldatei schliessen
	close (OUTDAT); # Zieldatei schliessen
}
Das Programm arbeitet nun mit zwei verschiedenen Dateien in unterschiedlichen Modi. Die Quelldatei wird lediglich zum Lesen geöffnet, während die Zieldatei zum Schreiben geöffnet wird. Auch hier wird die Quelldatei (in diesem Fall die ASCII Datei) zeilenweise eingelesen und mittels regulärer Ausdrücke (vgl. Teil 3 dieser Serie) bearbeitet.

Wie Sie sicher bemerkt haben, wird die eingelesene Zeile innerhalb der while- Schleife keiner Variablen zugewiesen, zumindest nicht sichtbar. In diesem Fall stehten die Informationen in der Sondervariablen $_, auf welche sich auch reguläre Ausdrücke als auch die print() Anweisung beziehen, sofern kein anderer Bezeichner angegeben ist. Da dies hier nicht der Fall ist, habe ich diese Art einmal ganz bewußt zur Demonstration gewählt. Auch wenn es auf den ersten Blick etwas verwirrend wirkt, zeigt dies doch auch wieder die Leisungsfähigkeit von Perl.

Perl ist nun sicher nicht der Inbegriff der übersichtlichen Programmierung, auch wenn ich mich bemühe, meine Beispiele recht übersichtlich zu gestalten, wird man in der Praxis kaum auf solche Sourcen stoßen. So würde das Programm demo5-7.pl denselben Zweck erfüllen, die demo5-6.pl, jedoch ist es deutlich kürzer und chotischer, obwohl es im Grunde genommen die gleichen Anweisungen enthält:

Programm: demo5-7-pl

#!/usr/bin/perl 
$fusszeile = "converted by ascii2html<BR>" ; print "Originaldatei: "; chop ($indat = <STDIN>); 
print "Neue Datei:    "; chop ($outdat = <STDIN>); print "HTML - Dokumenttitel:"; 
chop ($titel = <STDIN>);
open (INDAT,$indat); open (OUTDAT,"> $outdat");
print OUTDAT "<HTML>\n" ; print OUTDAT "<TITLE>$titel</TITLE>\n<BODY>";
while (<INDAT>) { s/&/&amp;/g; s/>/&gt;/g; s/</&lt;/g; s/\n/<BR>\n/g;
 s/\344/&auml;/g; s/\366/&ouml;/g; s/\374/&uuml;/g;
 s/\337/&szlig;/g; s/\304/&Auml;/g; s/\326/&Ouml;/g; s/\334/&Uuml;/g;
 print OUTDAT ;}
print OUTDAT "<P><HR>\n"; print OUTDAT "$fusszeile\n"; print OUTDAT "</BODY></HTML>"; 
close (INDAT); close (OUTDAT); 
Ich denke (und hoffe), daß ich Ihnen die Dateibehandlung unter Perl nun etwas näher bringen konnte und ansprechende Beispiele gefunden habe. Im nächsten Teil dieser Serie werden wir uns dann noch anssehen, wie man aus Perl heraus andere (meist System-) Programme ansprechen kann und deren Funktion einfach in eigene Programme integrieren kann.

Der Autor

Achim Schmidt ist staatlich geprüfter Assistent für Automatisierungs- und Computertechnik (HBFS) und beschäftigt sich bereits seit 6 Jahren intensiv mit dem Internet und seinen Diensten. Mit Linux wurde er erstmalig 1992 beim Aufbau eines Mailboxsystemes direkt konfrontiert. Seit 1995 ist er als Netzwerk- und Systemmanager tätig und beschäftigt sich dabei auch mir der Entwicklung und Realisierung WWW- basierter Onlinesysteme und Internetkopplungen. Zu erreichen ist er unter as@Saar.DE.

Copyright © 1997 Linux-Magazin Verlag