freeX: Programmierung mit Perl, Teil 2
|
Perl hier, Perl da, Perl ist immer da!
| |
Perl gehört zu Unix wie die Milch zu den Cornflakes. Obwohl die
Sprache nicht bei allen Derivaten von BSD und kommerziellen
Unix-Systemen zum direkten Lieferumfang gehört, ist sie dennoch die am
weitesten verbreitete Skript-Sprache auf Unix-artigen Systemen.
|
|
1. Was tun? - Dokumentation |
In der letzten Ausgabe der freeX wurde die Sprache vorgestellt. In
dieser Ausgabe sollen die Kenntnisse angewendet und erweitert werden.
Dazu werden verschiedene Beispielprogramme entwickelt und ausgebaut.
Was tun? - Dokumentation
Um mit Perl arbeiten zu können, reicht es natürlich nicht aus, die Syntax der Sprache zu verstehen. Das ist lediglich Handwerkszeug. Viel wichtiger ist es, passende Funktionen und Programme zu kennen - oder zu wissen, wie wo man sie findet.
Perl wird mit umfassender Dokumentation ausgeliefert. Dort findet man neben vielen Anregungen zur Problemlösung oftmals auch Code-Fragmente, die man so in die eigenen Programme übernehmen darf. Die Dokumentation liegt teilweise in Form von sogenannten Manpages vor und wird mit dem Programm »man« angezeigt (z.B. »man perldoc« oder »man perl«).
Ein Teil liegt in einem speziellen Perl-Format vor und wird mit »perldoc« angezeigt. Dabei findet man auch die Manpages. »perldoc« bietet jedoch auch die Möglichkeit, die Beschreibung einzelner Funktionen direkt anzuzeigen, ohne erst das gesamte Dokument durchsuchen zu müssen. Mit »perldoc -f open« wird die Beschreibung der Routine »open« ausgegeben, die weiter unten benutzt wird. Zusätzlich wird mit »perldoc -q« nach Schlüsselwörtern in der beiliegenden FAQ.
Es lohnt sich, die umfassende Dokumentation zu Perl und den Modulen zu
lesen, wenn man sich einarbeiten möchte. Um ein komplizierteres
Beispiel zu verstehen, werden weitere wichtige Eigenschaften von Perl
benötigt, z.B.
Dateioperationen
Was wären Programme, wenn sie nicht mit Dateien arbeiten könnten? Viele Aufgaben erfordern das Einlesen und Schreiben von Dateien, z.B. um Inhaltsverzeichnisse anzulegen, Abrechnungen zu erstellen u.s.w. Perl unterstützt die wichtigsten Dateioperationen mit einfacher Bedienung. Wer weniger Komfort, dafuer mehr Kontrolle benötigt, findet sie in »IO::Handle« und »IO::File«.
Wie in C auch, werden Dateien mit einem »open«-Befehl geöffnet und mit einem »close«-Befehl geschlossen. Beim Öffnen einer Datei wird sie einem sogenannten Datei-Handle, einer speziellen Variable, zugewiesen. Über dieses Handle wird anschließend aus der Datei gelesen oder in sie geschrieben.
Die Art, wie eine Datei geöffnet wird, bestimmt, ob sie zum Lesen oder zum Schreiben zur Verfügung steht. Der Begriff "Datei" ist hier etwas weiter gefaßt, denn es dürfen auch Programme verwendet werden, von denen gelesen wird oder in die geschrieben wird. Dabei wird die Ausgabe der Programme von Perl verwendet oder Perl liefert die Eingabe für die Programme.
Der einfachste Weg, eine Datei zu öffnen, geschieht mit der Anweisung »open (HANDLE, dateiname)«. Der Dateiname wird in einer Variablen übergeben oder direkt angegeben. Die Routine öffnet die Datei und weist sie dem Handle, das meistens zur besseren Lesbarkeit in Großbuchstaben geschrieben wird, zu. Zusätzlich wird »wahr« oder »falsch« zurückgegeben, so daß man im Perl-Programm abfragen kann, ob die Operation erfolgreich war.
Die folgenden Befehle sind gleichbedeutend:
$file = "/etc/passwd"; open (FOO, $file);
oder zusammengefaßt:
open (FOO, "/etc/passwd");
So zu programmieren, zeugt jedoch nicht nur von schlechtem Stil, sondern ist auch nicht sicher vor Fehlern. Wenn das Öffnen der Datei nicht möglich war, dann würde das Programm munter mit der Datei arbeiten, auf die es gar nicht zugreifen kann. Das mag beim Lesen von einer Datei vielleicht nicht allzu gefährlich sein, wenn jedoch eine Datei beschrieben werden soll, würde es bedeuten, daß die gesamte Ausgabe verloren ginge. Daher sollten bei solchen Operationen immer mögliche Fehler abgefangen werden, z.B. wie folgt:
if (open (FOO, "/etc/passwd") {
close (FOO);
} else {
}
| |
| Datei öffnen in Perl | |
Um in eine Datei zu schreiben, wird ein Größerzeichen vor den Dateinamen geschrieben. Wenn die Datei allerdings bereits existiert, wird sie überschrieben. Soll hingegen Text an eine bestehende Datei angehängt werden, so werden zwei Größerzeichen verwendet. Wenn es nicht gelingt, die Datei zum Schreiben zu öffnen, wird teilweise das gesamte Programm abgebrochen. Dafür wird die Routine »die« verwendet, mit der zusätzlich eine Fehlermeldung und die Zeilennummer ausgegeben wird. Das folgende Fragment versucht, eine Datei zum Schreiben zu öffnen:
open (FOO, ">/etc/passwd.new") ||
die "Can't open /etc/passwd.new, $!,";
Wenn die Datei »/etc/passwd.new« nicht geöffnet werden kann, z.B. weil das Programm als normaler Benutzer und nicht als »root« ausgeführt wird, dann wird es mit einer entsprechenden Fehlermeldung abgebrochen. Dieses ist in nebenstehender Abbildung zu sehen.
Ein Sonderfall liegt dann vor, wenn keine Datei aus dem Dateisystem geöffnet, sondern ein Programm gestartet wird. Die Anweisung »open(FOO, "/bin/ls|")« startet das Programm »/bin/ls« und liest von dessen Ausgabe. Dieses Beispiel implementiert man jedoch besser mit »opendir()« und »readdir()«. Soll das Perl-Skript die Eingabe für ein Programm liefern, dann wird das Pipe-Zeichen (»|«) vor den Befehl gesetzt.
Abbildung 1: Arrays sind eindimensionale Listen
Wurde eine Datei zum Lesen geöffnet, so bewirkt jede Verwendung von
» Eine Stärke von Perl besteht in der komfortablen Verwaltung von Arrays
und Hashes. Arrays sind eindimensionale Listen von Werten. Perl
vergrößert sie bei Bedarf automatisch, so daß sich der Programmierer
nicht mit solch "unwichtigen" Dingen wie Listenmanagement beschäftigen
muß. Die Elemente sind beliebige Perl-Objekte, Zeichenketten, Zahlen,
Arrays, Listen, Zeiger etc.
Das gesamte Array wird mit »@name« referenziert, auf die einzelnen
Elemente wird mit »$name[position]« zugegriffen. Dabei ist zu
beachten, daß bei 0 angefangen wird zu zählen. Das erste Element ist
daher »$name[0]«. Der Index des letzten Elements erhält man mit
»$#name«. Ergibt dieses 0, so hat das Array ein Element. Nur, wenn
-1 zurückgegeben wird, ist das Array leer und entspricht »()«.
Das wichtigste Array ist »@ARGV«. In ihm sind die Argumente
gespeichert, die an das Programm auf der Kommandozeile übergeben
wurden. Das erste Element (also »$ARGV[0]«) enthält das erste
Argument.
Hashes bzw. assoziative Arrays sind besondere Typen von Arrays. Die
Anzahl ihrer Elemente ist immer eine gerade Zahl. Es sind indizierte
Arrays. Jedes Element ist ein Paar (key,value). Über den Index wird
auf den Wert zugegriffen, hier werden jedoch geschweifte Klammern
verwendet. Mit den speziellen Funktionen »keys« und »values« wird
eine eindimensionale Liste der Indizes bzw. Werte erzeugt:
Viele Funktionen geben Arrays zurück, weswegen es wichtig ist, mit
ihnen umgehen zu können. Funktionen, die mehrere Werte zurückliefern,
haben Arrays als Rückgabewerte. Einige Funktionen liefern zudem
Zeichenketten oder einzelne Werte zurück, wenn die nachfolgende
Zuweisung diese erwarten, oder ein Array, falls das Ergebnis einem
Array zugewiesen wird.
Ein Großteil der Arbeiten, die mit Perl erledigt werden, betrifft das
Aufteilen von Zeichenketten nach bestimmten Regeln. Oftmals werden
Listen verarbeitet, dessen Felder durch Trennzeichen voneinander
getrennt sind. Die Paßwortdatei (»/etc/passwd«) trennt die einzelnen
Felder z.B. mit einem Doppelpunkt, die Filesystem-Table (»/etc/fstab«)
verwendet eine beliebige Anzahl von Blanks (Leerzeichen und
Tabulatoren) als Trennzeichen.
Sollen aus diesen Listen Berichte erstellt werden, dann müssen die
Felder zuerst aufgeteilt werden. Dieses geschieht am einfachsten mit
dem Befehl »split«, der eine gegebene Variable nach ebenfalls
angegebenen Trennzeichen durchsucht und die einzelnen Elemente in
einem Array speichert, das zurückgegeben wird. Als Trennzeichen
werden reguläre Ausdrücke akzeptiert. Das gibt die nötige Freiheit,
z.B. wenn ein oder mehrere Blanks verwendet werden.
Das folgende Programm verdeutlicht dieses. Es liest die Paßwortdatei
und gibt die Logins und Namen der Benutzer aus. Die System-Benutzer
wie »root«, »daemon« etc. werden übersprungen. Die Anweisung »next«
springt aus der aktuellen Schleife heraus und die Ausführung des
Programms wird am Ende der Schleife fortgesetzt. Ist die
Schleifenbedingung weiterhin gültig, wird die Schleife mit neuen
Werten erneut ausgeführt.
Nutzt man einen oft verwendeten Sonderfall aus, so läßt sich das
Programm vereinfachen. Perl verwendet die Standardvariable »$_«,
wenn nicht explizit eine Variable angegeben wurde und sonst auch keine
Eingabe zur Verfügung steht. Wird z.B. von einer Datei gelesen, ohne
daß die Zeile einer Variablen zugewiesen wird, dann hat Perl sie
implizit »$_« zugewiesen. Findet anschließend Pattern Matching oder
ein Split statt, ohne daß explizit eine Variable angegeben wurde, so
wird ebenfalls »$_« verwendet. Das Beispielprogramm von oben sieht
modifiziert wie folgt aus:
Dabei werden zwei Seiteneffekt ausgenutzt. Wird lediglich » «
geschrieben, so bedeutet es implizit »$_ = «, der Variablen »$_«
wird also die nächste Zeile aus der zu »P« gehörenden Datei
zugewiesen. Der zweite Seiteneffekt betrifft die Zuweisung, wenn sie
als Bedingung verwendet wird. Der logische Wert einer Zuweisung ist
normalerweise "wahr", es sei denn, es wird aus einer Datei gelesen und
das Ende dieser ist erreicht. Wenn der Dateizeiger am Ende der Datei
steht, und im Programm wird mit »while ( )« versucht, zu lesen, dann
ist der logische Wert der Zuweisung "falsch" und die
Schleifenbedingung trifft nicht mehr zu. Die Schleife wird daher
beendet.
Das Programm wird leicht modifiziert, so daß es nur noch Informationen
zu einem einzigen Benutzer ausgibt, ähnlich wie das Programm
»finger«. Der Name des Benutzers wird in der Befehlszeile übergeben.
Das Programm soll einen Fehler ausgeben, wenn kein Benutzer angegeben
wurde.
Neu sind hier zwei Konstrukte: »next until ()« und »print if ()«. Die
Anweisung »next« überspringt den Schleifenkörper, daher muß sie an
eine Bedingung gekoppelt werden. Das Fragment »until (bedingung)«
bedeutet, daß die daran geknüpfte Anweisung solange ausgeführt
wird, bis die Bedingung "wahr" ergibt. Erst dann werden die
nachfolgenden Anweisungen in der Schleife ausgeführt.
Da das Namensfeld in der Paßwort-Datei erneut eine Liste von vier
Feldern sein kann, wird das Feld ebenfalls mit »split« in ein Array
aufgeteilt. Sollte das Namensfeld leer sein, so ist auch das Array
»@name« leer. Daher ist »$name[0]« eine leere Zeichenkette, wenn eine
Zeichenkette erwartet wird, oder 0, wenn eine Zahl erwartet wird
(z.B. bei »printf "%d\n", $name[0]«).
Die drei folgenden »printf«-Anweisungen werden nur dann ausgeführt,
wenn ausreichend Elemente in diesem Array vorhanden sind. In diesem
Fall wurde die »if«-Bedingung in bisher unbekannter Reihenfolge
geschrieben, denn die Verzweigung folgt auf den Befehl und nicht
umgekehrt. Eine "if"-Verzweigung läßt sich bequem abkürzen, wenn die
eigentliche Verzweigung hinter der Anweisung geschrieben wird.
Die folgenden Code-Fragmente sind daher äquivalent:
Wenn nur eine einzige Anweisung von der Bedingung abhängt, wird meist
die kürzere Variante verwendet.
Bisher ist bekannt, wie Dateien geöffnet werden und wie aus ihnen
gelesen wird, jedoch nicht, wie in sie geschrieben wird. Dazu muß man
sich die Syntax der »print«-Befehle vor Augen führen:
sowie
Wenn in Beispielen bisher »print«-Befehle benutzt wurden, so sah die
Verwendung jedoch anders aus. Das »HANDLE« war nicht zu sehen. Das
liegt daran, daß Perl auch hier ein implizites Handle verwendet, wenn
nicht explizit eines angegeben wird. Daher bedeutet:
soviel wie
Die Ausgabe erfolgt also auf der Standard-Ausgabe, also auf der
Konsole oder im xterm. Das gleiche gilt für das Lesen aus Dateien.
Wird im Programm »<>« verwendet, so wird implizit die Standard-Eingabe
verwendet. Die Anweisung ist daher äquivalent zu » Das folgende Beispiel kopiert den Eintrag eines Benutzers aus der
Paßwort-Datei in eine Datei im lokalen Verzeichnis. Dazu wird die
Datei »onepasswd« zum Schreiben geöffnet.
Die Bedeutung dieses Programms sollte klar sein. In die neue Datei
wird mit der Anweisung »print OUT« geschrieben. Doch was wird dort
geschrieben? Dieses mal ist zwar ein Datei-Handle angegeben, jedoch
kein Text. Hier kommt Perl wieder mit impliziten Variablen ins Spiel,
denn wenn keine Variable angegeben wurde, wird automatisch »$_«
verwendet. Was in der Variablen enthalten ist, wurde oben bereits
erläutert.
Zur Verdeutlichung wird das Programm so modifiziert, daß anstelle von
»print OUT« nur noch »print« geschrieben wird. Wie ist dann das
Verhalten des Programms?
Arrays und Hashes
Abbildung 2: Ein Hash ist eine zweidimensionale Liste
@keys = keys %ENV;
@values = values %ENV;
foreach $key (@keys) {
printf "%s=%s\n", $key, $ENV{$key};
}
Hash- und Arrayfunktionen
Stringverarbeitung
if (open (P, "/etc/passwd")) {
@passwd = <P>;
close (P);
foreach $line (@passwd) {
@array = split (/:/, $line);
next if ($array[2] < 100);
printf "%s\t%s\n", $array[0], $array[4];
}
}
Verarbeiten einer einfachen Textdatei
if (open (P, "/etc/passwd")) {
while (<P>) {
@array = split (/:/);
next if ($array[2] < 100);
printf "%s\t%s\n", $array[0], $array[4];
}
close (P);
}
Inline-Verarbeitung einer Datei
Beispiel: Finger
die "Kein Benutzer angegeben," if ($#ARGV == -1);
if (open (P, "/etc/passwd")) {
while (<P>) {
@array = split (/:/);
next until ($array[0] eq $ARGV[0]);
printf "Login: %s\n", $array[0];
@name = split (/,/,$array[4]);
printf " Name .....: %s\n", $name[0];
printf " Office ...: %s\n", $name[1] if ($#name> 0);
printf " Work phone: %s\n", $name[2] if ($#name> 1);
printf " Home phone: %s\n", $name[3] if ($#name> 2);
printf " Home .....: %s\n", $array[5];
printf " Shell ....: %s\n", $array[6];
}
close (P);
}
Benutzerinformationen mit Perl ermitteln
printf " Office ...: %s\n", $name[1] if ($#name> 0);
if ($#name > 0) {
printf " Office ...: %s\n", $name[1];
}
Schreiben in Dateien
print HANDLE "text"
printf HANDLE "format", argument [argument, ...]
print "text\n"
print STDOUT "text\n"
die "Kein Benutzer angegeben," if ($#ARGV == -1);
open (OUT, ">onepasswd") || die "Can't open onepasswd,";
if (open (P, "/etc/passwd")) {
while (<P>) {
@array = split (/:/);
next until ($array[0] eq $ARGV[0]);
print OUT;
}
close (P);
close (OUT);
}
Benutzerinformationen kopieren
Quelle: freeX 2/00