TOC PREV Übungen NEXT INDEX

6 I/O-Grundlagen

Um um einige der früheren Übungen zu ermöglichen, haben wir bereits einige Formen der Ein- und Ausgabe (I/O) vorgestellt. Jetzt werden wir über diese Operationen etwas mehr sagen. Wie der Titel dieses Kapitels andeutet, gibt es hierüber noch einiges mehr zu sagen. Das geschieht in Kapitel 11.

Eingaben von der Standardeingabe (STDIN)

Von der Standardeingabe zu lesen ist recht einfach.1 Hierfür haben wir bereits den <STDIN>-Operator2 benutzt. Die Auswertung dieses Operators in skalarem Kontext gibt Ihnen jeweils die folgende Eingabezeile:

 $zeile = <STDIN>;       	# nächste Zeile einlesen 
 chomp($zeile);          	# mittels chomp Newline-Zeichen entfernen
 
 chomp($zeile = <STDIN>);	# das gleiche, aber idiomatischer

Da der Zeileneingabe-Operator beim Erreichen des Dateiendezeichens undef zurückgibt, läßt er sich sehr praktisch zum Beenden einer Schleife einsetzen:

 while (defined($zeile = <STDIN>)) {
   print "$zeile habe ich bereits gesehen!";  
 }

Zuerst lesen wir die Eingabe in eine Variable ein und überprüfen, ob sie definiert ist. Ist dies der Fall (was bedeutet, daß wir das Ende der Eingabe noch nicht erreicht haben), führen wir den Schleifenkörper aus. Innerhalb des Schleifenkörpers finden sich die Eingabezeilen nacheinander in der Variablen $zeile wieder.3 Diese Vorgehensweise kommt in Perl so oft vor, daß es auch hierfür eine Abkürzung gibt:

 while (<STDIN>) {
   print "$_ habe ich bereits gesehen!";
 }

Für diese Abkürzung hat Larry eine eigentlich nutzlose Schreibweise gewählt. Wörtlich heißt das Obenstehende: »Lies eine Eingabezeile, überprüfe, ob sie wahr ist. (Normalerweise ist sie das.) Führe in diesem Fall die while-Schleife aus, und verwirf die Eingabezeile wieder !« Larry wußte, daß dies eigentlich nutzlos ist; niemand sollte so etwas in einem echten Perl-Programm jemals tun müssen. Also wandelte er diese nutzlose Syntax in etwas Sinnvolles um.

Tatsächlich bedeutet die Schreibweise, daß Perl das gleiche tun soll wie in unserer ersten Schleife: Die Eingabe wird in eine Variable eingelesen und die while-Schleife ausgeführt (letzteres, solange das Ergebnis definiert ist, also noch Zeilen zu bearbeiten sind). Anstatt die Zeile allerdings in $zeile abzulegen, benutzt Perl seine beliebteste Standardvariable, $_, als hätten Sie das Folgende geschrieben:

 while (defined($_ = <STDIN>)) {
   print "$_ habe ich bereits gesehen!";
 }

Bevor wir nun weitergehen, müssen Sie sich über eine Sache klar sein: Diese Abkürzung funktioniert nur, wenn sie so geschrieben wird, wie wir es hier getan haben. Wenn Sie den Zeileneingabe-Operator an einer anderen Stelle (insbesondere in einer alleinstehenden Anweisung) benutzen, wird die Zeile nicht standardmäßig in $_ gespeichert. Die Abkürzung funktioniert ausschließlich, wenn nur der Zeileneingabe-Operator im Bedingungsteil der Schleife steht.4 Sobald Sie im Bedingungsteil der while-Schleife noch etwas anderes schreiben müssen, funktioniert diese Abkürzung nicht mehr.

Zwischen dem Zeileneingabe-Operator (<STDIN>) und der beliebtesten Standardvariablen von Perl ($_ ) besteht keinerlei Beziehung. In diesem Fall wird nur einfach die Eingabe in dieser Variablen gespeichert.

Wenn Sie den Zeileneingabe-Operator jedoch im Listenkontext auswerten, so bekommen Sie alle (verbleibenden) Zeilen als Liste zurückgeliefert - jedes Element der Liste enthält eine Zeile:

 foreach (<STDIN>) {
   print "$_ habe ich bereits gesehen!";
 }

Auch hier gibt es keine Verbindung zwischen dem Zeileneingabe-Operator und der Variablen $_. In diesem Fall ist $_ die Standardvariable für die foreach-Schleife. In dieser Schleife sehen wir also jede Eingabezeile nach der anderen in $_.

Das kommt uns aus einem guten Grund bekannt vor. Richtig! Das ist das gleiche Verhalten wie in der while-Schleife.

Der Unterschied ist hinter den Kulissen zu finden. In der while-Schleife liest Perl eine Eingabezeile ein und führt den Schleifenkörper aus. Dann erst wird die nächste Zeile eingelesen. In der foreach-Schleife benutzen wir den Zeileneingabe-Operator jedoch im Listenkontext (da foreach eine Liste benötigt, über die es iterieren kann). Bevor die Schleife starten kann, müssen also alle Eingaben eingelesen worden sein. Der Unterschied wird offensichtlich, wenn die Eingabe aus einer 400 MB großen Log-Datei eines Webservers kommt! In der Regel ist es also besser, hier eine while-Schleife zu benutzen, da hier nach Möglichkeit immer nur jeweils eine Zeile bearbeitet wird.

Eingaben vom Diamantoperator

Eine andere Möglichkeit, Daten einzulesen, besteht in der Verwendung des Diamantoperators5, <>. Dieser Operator ist hilfreich, wenn es darum geht, Programme zu schreiben, die sich wie Unix6-Standardhilfsprogramme verhalten. Diese können mit verschiedenen Argumenten aufgerufen werden (wie wir gleich sehen werden). Wenn Sie ein Perl-Programm schreiben wollen, das sich wie die Utilities cat, sed, awk, sort, grep, lpr und viele andere verhält, ist der Diamantoperator Ihr Freund. In anderen Fällen wird er Ihnen vermutlich nicht besonders viel nützen.

Die aufrufenden Argumente, die einem Programm bei seinem Aufruf mitgegeben werden können, sind normalerweise eine Reihe von »Wörtern« auf der Kommandozeile, die nach dem Namen des Programms angegeben werden.7 In diesem Fall geben wir ein paar Namen von Dateien an, deren Inhalt das Programm verarbeiten soll:

 $ ./mein_programm fred barney betty

Dieses Kommando weist das System an, das Programm mein_programm (das sich im gegenwärtigen Verzeichnis befindet) auszuführen. Dieses Programm soll die Dateien fred, barney und betty nacheinander bearbeiten.

Falls Sie einem Programm keine aufrufenden Argumente mitgeben, sollte es statt dessen mit dem Standardeingabekanal arbeiten. In manchen Fällen können Sie auch einen Bindestrich (-) angeben, der ebenfalls für die Standardeingabe steht.8 Wären die aufrufenden Argumente also fred - betty gewesen, so hätte das Programm zuerst die Datei fred bearbeitet, dann die Standardeingabe und zum Schluß die Datei betty.

Der Vorteil dieser Arbeitsweise besteht darin, daß Sie erst zur Laufzeit des Programms entscheiden müssen, von wo das Programm seine Eingaben empfängt. Sie müssen das Programm also nicht extra umschreiben, wenn Sie es in einer Pipe (auf die wir später genauer eingehen) verwenden wollen. Larry hat Perl dieses Feature mitgegeben, da er es Ihnen leicht machen wollte, Programme zu schreiben, die wie Standard-Utilities funktionieren.9

Der Diamantoperator ist also eigentlich eine besondere Art von Zeileneingabe-Operator. Anstatt seine Eingaben von der Tastatur zu bekommen, kann hier der Benutzer die Eingabequelle selbst wählen.10

 while (defined ($zeile = <>)) {
   chomp($zeile);
   print "$zeile habe ich bereits gesehen!\n";
 }

Wenn wir also das Programm mit den aufrufenden Argumenten fred, barney und betty auführen, sieht die Ausgabe etwa so aus: »[eine Zeile aus der Datei fred ] habe ich bereits gesehen!« und so weiter, bis das Ende der Datei fred erreicht ist. Danach wird automatisch die Datei barney zeilenweise ausgegeben und schließlich auch betty. Hierbei werden alle Dateien direkt hintereinander ausgegeben. Die Ausgabe sieht also aus, als wären alle Eingabedateien zu einer großen Datei zusamengefügt worden.11 Erst wenn alle Eingabedateien abgearbeitet sind, gibt der Diamantoperator undef zurück, und die while-Schleife wird beendet.

Da es sich hierbei um eine Sonderform des Zeileneingabe-Operators handelt, können wir hier die gleiche Abkürzung benutzen. Wie bereits gezeigt, speichern wir auch hier die Eingabezeile standardmäßig in $_ :

 while (<>) {
   chomp;
   print "$_ habe ich bereits gesehen!\n";
 }

Die Funktionsweise ist die gleiche wie bei der obenstehenden Schleife, nur mit weniger Tipparbeit. Vermutlich ist Ihnen aufgefallen, daß wir hier das Standardverhalten von chomp benutzt haben: Wird chomp ohne Argumente aufgerufen, wird standardmäßig $_ benutzt. Jedes bißchen vermiedene Tipperei ist eine Hilfe!

Da der Diamantoperator in der Regel dazu benutzt wird, sämtliche Eingaben zu bearbeiten, gilt es als Fehler, ihn in einem Programm an mehr als einer Stelle zu verwenden. Wenn Sie feststellen, daß Sie in Ihrem Programm zwei Diamantoperatoren haben, wird Ihr Programm so gut wie nie das tun, was Sie erwarten. Dies ist vor allem dann der Fall, wenn der zweite Diamantoperator innerhalb der while-Schleife steht, die aus dem ersten Diamantoperator lesen soll.12 Unserer Erfahrung nach meinen Anfänger, die einen zweiten Diamantoperator in ihrem Programm stehen haben, fast immer die Standardvariable $_. Denken Sie daran: Der Diamantoperator liest die Eingabedaten, während die Daten selbst (normalerweise, standardmäßig) in $_ zu finden sind.

Kann der Diamantoperator eine Datei nicht öffnen oder aus ihr lesen, so gibt er eine entsprechende Warnung aus:

 can't open wimla: No such file or directory

Anstatt das Programm zu beenden, macht der Diamantoperator einfach mit der nächsten Datei weiter, so wie Sie es auch von Standard-Utilities, wie etwa cat, erwarten würden.

Aufrufende Argumente

Technisch gesehen, benutzt der Diamantoperator nicht die aufrufenden Werte selbst, sondern die Werte, die in @ARGV stehen. Dies ist ein spezielles Array, in dem Perl die Liste der aufrufenden Argumente ablegt. Mit anderen Worten: @ARGV verhält sich genau wie jedes andere Array auch (wenn man einmal von seinem nur aus Großbuchstaben bestehenden Namen absieht).

Wird jetzt allerdings das Programm gestartet, ist @ARGV bereits gestopft voll mit aufrufenden Argumenten.13

Sie können @ARGV genauso benutzen wie jedes andere Array auch; Sie können Elemente per shift entfernen oder mit einer foreach-Schleife darüber iterieren. Sie können sogar überprüfen, ob die Argumente mit einem Bindestrich beginnen (wie zum Beispiel die Perl-Option -w) und diese als Optionen zum Steuern Ihres Programms einsetzen.14

Der Diamantoperator bekommt seine Informationen über die zu verwendenden Dateinamen aus dem Array @ARGV. Enthält dieses eine leere Liste, wird die Standardeingabe benutzt. Hierdurch haben Sie die Möglichkeit, @ARGV zu verändern, bevor Sie den Diamantoperator anwenden. Im untenstehenden Beispiel bearbeiten wir drei Dateien, unabhängig davon, was der Benutzer auf der Kommandozeile angegeben hat:

 @ARGV = qw# larry moe curly #;  # drei Dateien zum Lesen hartcodieren
 while (<>) {
   chomp;
   print "Ich habe $_ in einer Handlangerdatei gesehen!\n";
 }

In Kapitel 11 werden wir sehen, wie Sie das Öffnen und Schließen von Dateien genauer steuern können. Für die folgenden Kapitel wird die hier gezeigte Technik jedoch ausreichen.

Ausgaben auf STDOUT

Der print-Operator nimmt eine Liste von Werten und schreibt sie nacheinander (als String, versteht sich) in die Standardausgabe (STDOUT). Es werden keine zusätzlichen Zeichen vor, nach oder zwischen den Elementen eingefügt;15 wenn Sie Leerzeichen zwischen den einzelnen Elementen wollen, so müssen Sie das auch sagen:

 $name = "Larry Wall";
 print "Hallo $name, wusstest Du, daß 3+4 zusammen ", 3+4, " ergibt?\n";

Es besteht natürlich ein Unterschied zwischen der direkten Ausgabe eines Arrays und dem Interpolieren eines Arrays:

 print @array;     # gibt eine Liste der Elemente aus
 print "@array";   # gibt einen String aus (der ein
                   # interpoliertes Array enthält)

Die erste print-Anweisung gibt die Elemente der in @array gespeicherten Liste nacheinander aus. Hierbei werden die Elemente nicht durch Leerzeichen voneinander getrennt. Die zweite print-Anweisung gibt exakt ein Element aus, nämlich den String, der sich ergibt, wenn Sie das @array in den leeren String interpolieren. Diesmal werden die Elemente durch Leerzeichen voneinander getrennt.16 Stünde in @array also etwa qw/ Fred Barney Betty /,17 so gäbe die erste Anweisung FredBarneyBetty aus, während die zweite Fred Barney Betty durch Leerzeichen getrennt ausgibt.

Bevor Sie sich jetzt aber vorschnell entscheiden, immer die zweite Form zu benutzen, sollten Sie sich das @array einmal als Liste von Zeilen vorstellen, die alle auf ein Newline-Zeichen enden (auf die noch kein chomp angewandt wurde). Jetzt gibt die erste print-Anweisung Fred, Barney und Betty jeweils auf einer eigenen Zeile aus. Das Ergebnis der zweiten Anweisung sieht aber nun folgendermaßen aus:

 Fred
  Barney
  Betty

Können Sie erkennen, woher die Leerzeichen kommen? Perl interpoliert ein Array, also trennt es die einzelnen Elemente durch Leerzeichen. Die Ausgabe besteht also aus Fred und einen Newline-Zeichen (dem ersten Element), dann einem Leerzeichen gefolgt vom nächsten Element (Barney und einem Newline-Zeichen), einem weiteren Leerzeichen und dem letzten Element (Betty und einem Newline-Zeichen). Dadurch werden alle Zeilen bis auf die erste eingerückt. Mindestens ein- bis zweimal die Woche erscheint eine Nachricht in der Newsgroup comp.lang.perl.misc mit der folgenden oder einer ähnlichen Betreffzeile:

Perl rückt alles nach der ersten Zeile ein

Ohne die Nachricht überhaupt lesen zu müssen, können wir bereits sagen, daß ein Array in doppelten Anführungszeichen benutzt wurde, in dem Strings mit einem Newline-Zeichen am Ende stehen. Wenn wir dann nachfragen, ob es so war, ist die Antwort immer ja.

Enthalten Ihre Strings Newline-Strings, wollen Sie sie in der Regel sowieso einfach nur ausgeben:

 print @array;

Enthält die Liste dagegen noch keine Newline-Zeichen, so wollen Sie normalerweise eines am Ende einfügen:

 print "@array\n";

Das sollte Ihnen helfen, sich zu merken, womit Sie es gerade zu tun haben.

Normalerweise werden die Ausgaben Ihres Programms gepuffert. Anstatt jedes einzelne Zeichen sofort auszugeben, wird hierbei gewartet, bis genügend Daten vorhanden sind, um sie effizient weiterzuverarbeiten. Wollen Sie Ihre Ausgaben beispielsweise auf einer Festplatte speichern, wäre es (relativ) langsam, jedesmal einen Schreibvorgang auf die Platte auszulösen, wenn nur ein oder zwei Zeichen an eine Datei angehängt werden müssen. Daher wird die Ausgabe normalerweise zuerst in einem Puffer zwischengespeichert. Dieser wird geleert (das heißt: tatsächlich auf die Festplatte oder was auch immer geschrieben), sobald er voll ist oder wenn die Ausgabe anderweitig beendet wird (etwa am Ende der Laufzeit des Programms). In den meisten Fällen sollten Sie hiermit keine Probleme bekommen.

Wenn Sie (oder Ihr Programm) aber nun ungeduldig darauf warten, die Daten endlich auszugeben, kann es sein, daß Sie lieber Performance-Einbußen hinnehmen, um die Daten bei jedem print-Vorgang sofort ausgeben zu können. Die nötigen Informationen über die Kontrolle des Ausgabe-Pufferns finden Sie in den entprechenden Manpages.

Da print eine Liste von Strings erwartet, werden seine Argumente auch im Listenkontext ausgewertet. Da der Diamantoperator (als spezieller Zeileneingabe-Operator) im Listenkontext seinerseits eine Liste von Strings zurückgibt, funktioniert deren Zusammenarbeit recht gut:

 print <>;       # Quellcode für 'cat'
 print sort <>;  # Quellcode für 'sort'

Der Fairness halber müssen wir zugeben, daß die Standard-Unix-Programme cat und sort einige zusätzliche Funktionen aufweisen, die unsere Ersatzprogramme nicht haben. Aber der Preis ist unschlagbar! Jetzt können Sie alle Ihre Standard-Unix-Programme in Perl neu implementieren und sie streßfrei auf jedes andere System portieren, ob da nun Unix läuft oder nicht. Und Sie können sicher sein, daß die Programme auf einer beliebigen Maschine immer das gleiche Verhalten zeigen.18

Es ist vielleicht nicht ganz offensichtlich, aber print hat optionale runde Klammern, was gelegentlich Verwirrung stiften kann. Denken Sie an die Regel, nach der in Perl runde Klammern immer dann weggelassen werden können, sofern sich die Bedeutung der Anweisung dadurch nicht ändert. Hier haben wir zwei Möglichkeiten, das gleiche auszugeben:

 print("Hallo Welt!\n");
 print "Hallo Welt!\n";

Eine andere Regel in Perl besagt: Wenn ein Aufruf von print wie ein Funktionsaufruf aussieht, so ist es auch einer. Bei einem Funktionsaufruf folgen die Argumente, die zu der Funktion gehören, unmittelbar19 auf ihren Namen. Sie stehen dabei in runden Klammern, so wie hier:

 print (2+3);

Das sieht aus wie ein Funktionsaufruf, also ist es auch einer. Hier wird zuerst 5 aus-gegeben, und dann wird, wie bei jeder Funktion, ein Wert zurückgegeben. Der Rückgabewert von print ist entweder wahr oder falsch, je nachdem, ob die Ausgabe erfolgreich war. Die Ausgabe ist so gut wie immer erfolgreich, es sei denn, es gibt einen I/O-Fehler. Das Ergebnis der folgenden Anweisung ist also normalerweise 1 (wahr):

 $ergebnis = print("Hallo Welt!\n");

Was wäre aber nun, wenn Sie das Ergebnis auf eine andere Art benutzt hätten? Nehmen wir einmal an, Sie wollten den Rückgabewert mit vier multiplizieren:

 print (2+3)*4;  # Hoppla!

Wenn Perl diese Codezeile sieht, gibt es 5 aus, genau wie Sie es angewiesen haben. Dann wird der Rückgabewert der print-Anweisung (1) mit 4 multipliziert. Zum Schluß verwirft Perl das Produkt und fragt sich, warum Sie es nicht angewiesen haben, irgend etwas anderes damit zu tun. Genau an dieser Stelle wird Ihnen jemand über die Schulter sehen und sagen: »Ha! Perl beherrscht ja nicht einmal die Grundrechenarten! Dabei hätte doch 20 und nicht 5 herauskommen müssen!«

Bei der Benutzung von runden Klammern vergessen wir Menschen leicht, wo die Klammern tatsächlich hingehören. Gibt es keine runden Klammern, so arbeitet print als Listen-Operator, indem er erwartungsgemäß alle Elemente der folgenden Liste ausgibt. Folgt jedoch direkt auf print eine linke runde Klammer, wird print als Funktionsaufruf angesehen und gibt nur das aus, was innerhalb der Klammern steht. Da in der letzten Zeile Klammern vorkamen, ist es für Perl das gleiche, als hätten Sie gesagt:

 ( print(2+3) ) * 4;  # Hoppla!

Zum Glück kann Perl Ihnen bei den meisten Problemen dieser Art helfen - wenn Sie die Option -w benutzen. Zumindest beim Entwickeln und Debuggen sollte Ihnen das zur Gewohnheit werden.

Die Regel »Wenn etwas aussieht wie ein Funktionsaufruf, dann ist es auch einer« gilt für alle Listen-Funktionen in Perl20, nicht nur für print. Bei print ist es nur am wahrscheinlichsten, daß Ihnen dieses Verhalten auffällt. Folgt also auf print (oder einen anderen Funktionsnamen) eine linke runde Klammer, sollten Sie dafür sorgen, daß die schließende (rechte) Klammer erst nach allen Argumenten für diese Funktion kommt.

Formatierte Ausgaben mit printf

Manchmal werden Sie sich wünschen, Sie hätten etwas mehr Kontrolle über das Aussehen Ihrer Ausgaben, als print es Ihnen erlaubt. Falls Ihnen die Funktion printf bereits von der Programmiersprache C her bekannt ist, haben Sie keine Angst - in Perl gibt es eine vergleichbare Funktion mit dem gleichen Namen.

Der Operator printf übernimmt einen String, der das Format angibt, gefolgt von einer Liste der Dinge, die ausgegeben werden sollen. Der Formatierungsstring21 funktioniert wie eine Schablone, die angibt, wie die Ausgaben auszusehen haben:

 printf "Hallo %s. Dein Passwort ist noch %d Tage gueltig!\n",
   $benutzer, $tage_zu_leben;

Der Formatierungsstring enthält eine Reihe sogenannter Konversionen; jede Konversion beginnt dabei mit einem Prozentzeichen und endet auf einen Buchstaben. (Wie wir gleich sehen werden, können zwischen diesen beiden Symbolen eine Reihe von wichtigen Zusatzzeichen stehen.) Die Liste, die auf den Formatierungsstring folgt, sollte immer genauso viele Elemente enthalten, wie es Formatanweisungen gibt. Ist dies nicht der Fall, wird die Ausgabe ziemlich sicher anders aussehen, als Sie es erwarten. Im obenstehenden Beispiel gibt es zwei Elemente und zwei Konversionen. Diese erzeugen die folgende Ausgabe:

 Hallo merlyn. Dein Passwort ist noch 3 Tage gültig!

Die Zahl der bei printf möglichen Konversionen ist recht hoch. Hier werden wir jedoch nur die gebräuchlichsten behandeln. Die vollständige Dokumentation finden Sie selbsverständlich in der perlfunc-Manpage.

Die Konversion %g22 gibt eine Zahl automatisch in einem passenden Format aus. Es wird je nach Bedarf automatisch eine Fließkomma-, Integer- oder sogar eine Exponential-Notation auswählt.

 printf "%g %g %g\n", 5/2, 51/17, 51 ** 17;  # 2.5 3 1.0683e+29

Das Format %d steht für einen ganzzahligen Dezimalwert23, der nachgestellte Kommastellen bei Bedarf entfernt:

 printf "in %d Tagen!\n", 17.85;  # in 17 Tagen!

Die Kommastellen werden hierbei tatsächlich abgeschnitten, nicht gerundet; Sie werden gleich sehen, wie Sie eine Zahl runden können.

In Perl wird printf am häufigsten dafür verwendet, Daten spaltenweise auszugeben, da für die meisten Formate auch eine Feldbreite angegeben werden kann. Passen die Daten nicht ganz hinein, so wird das Feld normalerweise je nach Bedarf erweitert:

 printf "%6d\n", 42;  # Eine Ausgabe wie ¤¤¤¤42 (das ¤-Zeichen steht
                      # hier für einen Leerschritt)
 printf "%2d\n", 2e3 + 1.95;  # 2001

Die Konversion %s steht für einen String. Der angegebene Wert wird hierbei effektiv als String interpoliert, allerdings mit einer festen Feldbreite:

 printf "%10s\n", "Wilma";  # wird dargestellt als ¤¤¤¤¤ Wilma

Ein negativer Wert für die Feldbreite sorgt dafür, daß die Daten linksbündig ausgegeben werden (das gilt für alle Konversionen):

 printf "%-15s\n", "Feuerstein";  # ergibt Feuerstein¤¤¤¤¤

Die %f-Konversion (Fließkomma) rundet Ihre Ausgabe nach Bedarf auf oder ab und läßt Sie sogar die Anzahl der Stellen nach dem Komma angeben:

 printf "%12f\n",   6 * 7 + 2/3;  # ergibt ¤¤¤42.666667
 printf "%12.3f\n", 6 * 7 + 2/3;  # ergibt ¤¤¤¤¤¤¤42.667
 printf "%12.0f\n", 6 * 7 + 2/3;  # ergibt ¤¤¤¤¤¤¤¤¤¤¤¤¤43

Um ein literales Prozentzeichen auszugeben, benutzen Sie die Notation %%. Dieses Format unterscheidet sich insofern von den anderen, als daß hierfür kein Element aus der Liste der zu formatierenden Strings benutzt wird.24

 printf "Monatlicher Zinssatz: %.2f%%\n", 5.25/12;  
 # ergibt "Monatlicher Zinssatz: 0.44%"

Arrays und printf

Normalerweise wird printf nicht mit einem Array als Argument benutzt. Das liegt daran, daß ein Array eine beliebige Anzahl von Elementen enthalten kann, während ein Format-String nur mit einer festgelegten Anzahl von Elementen umgehen kann. Sind im Format drei Konversionen angegeben, so müssen auch genau drei Elemente benutzt werden.

Es gibt allerdings keinen Grund, das Format nicht erst dann zusammenzubauen, wenn alle nötigen Informationen (die Anzahl der Elemente) vorliegen; schließlich kann als Format jeder beliebige Ausdruck benutzt werden. Das ist manchmal nicht ganz einfach. Daher kann es (besonders beim Debuggen) nützlich sein, das Format in einer Variablen zu speichern.

 my @elemente = qw( wilma dino pebbles );
 my $format = "Die Elemente sind:\n" . ("%10s\n" x @elemente);
 # Zum Debuggen benutzen Sie die folgende Zeile:
 # print "Verwendetes Format: <<$format>>\n";
 printf $format, @elemente;

Dieses Beispiel verwendet den x-Operator (den wir aus Kapitel 2, Skalare Daten, bereits kennen), um einen gegebenen String zu wiederholen. Dies geschieht so oft, wie es die Anzahl der Elemente in @elemente (das hier in skalarem Kontext benutzt wird) vorgibt. Das Ergebnis ist 3, da das Array drei Elemente enthält. Der daraus resultierende Formatierungsstring ist der gleiche, als hätten wir geschrieben: "Die Elemente sind:\n%10s\ n%10s\n%10s\n." Nun wird jedes Element auf einer zehn Zeichen breiten Zeile rechtsbündig ausgegeben. Außerdem hat die Ausgabe auch gleich noch eine Überschrift bekommen. Ziemlich cool, was? Aber noch nicht cool genug, denn wir können die beiden Zeilen auch noch miteinander kombinieren:

 printf "Die Elemente sind:\n".("%10s\n" x @elemente), @elemente;

Beachten Sie, daß wir @elemente hier einmal in skalarem Kontext benutzt haben, um die Anzahl der Elemente zu ermitteln, und einmal im Listenkontext, um an seinen Inhalt heranzukommen. Der Kontext ist wichtig.

Übungen

Die Lösungen zu den folgenden Aufgaben finden Sie in Anhang A:

  1. [7] Schreiben Sie ein Programm, das wie cat funktioniert, die Reihenfolge der auszugebenden Zeilen jedoch umkehrt. (Auf manchen Systemen gibt es ein Utility mit dem Namen tac.) Wenn Sie Ihr Programm mit der Anweisung ./tac fred barney betty aufrufen, sollte zuerst die Datei betty von der letzten Zeile bis zur ersten ausgegeben werden, dann barney und dann fred - ebenfalls von der letzten Zeile bis zur ersten. (Achten Sie darauf, beim Programmaufruf ./ zu benutzen, wenn Sie es tac genannt haben, damit Sie nicht statt dessen das Utility des Systems aufrufen!)
    Zur Lösung
  2. [8] Schreiben Sie ein Programm, das den Benutzer dazu auffordert, eine Liste von Strings auf separaten Zeilen einzugeben. Die Strings sollen in einer rechtsbündigen Spalte mit einer Breite von 20 Zeichen ausgegeben werden. Um sicherzugehen, daß die Ausgabe auch in der richtigen Spalte erfolgt, soll zusätzlich ein »Lineal« aus Zahlen ausgegeben werden. (Dies ist nur als Debugging-Hilfe gedacht.) Stellen Sie sicher, daß Sie nicht versehentlich eine Zeile aus nur 19 Zeichen benutzen! Gibt der Benutzer zum Beispiel Hallo und Tschüß ein, so sollte die Ausgabe etwa so aussehen:
 123456789012345678901234567890123456789012345678901234567890
                Hallo
               Tschüß
    Zur Lösung
  1. [8] Ändern Sie das Programm aus der vorigen Übung so ab, daß der Benutzer die Spaltenbreite selbst wählen kann. Gibt der Benutzer also etwa 30 Hallo Tschüß (jeweils auf einer eigenen Zeile) ein, soll der String in einer dreißig Zeichen breiten Spalte ausgegeben werden. (Tip: Sehen Sie sich den Abschnitt »Interpolation von Skalarvariablen in Strings« in Kapitel 2 noch einmal an.) Zusatzpunkte gibt es, wenn das »Lineal« automatisch verlängert wird, wenn die vom Benutzer angegebene Spaltenbreite das verlangt.
    Zur Lösung

Fußnoten

1

Wenn Sie sich bereits mit den Kanälen für die Standardeingabe, -ausgabe und -fehlerausgabe auskennen, sind Sie diesem Buch schon weit voraus. Wenn nicht, werden wir diese Dinge in Kapitel 14 klären. Für den Moment können Sie sich die »Standardeingabe« einfach als »die Tastatur« und die »Standardausgabe« einfach als »den Bildschirm« vorstellen.

2

Was wir hier den Zeileneingabe-Operator, <STDIN>, nennen, ist eigentlich der Zeileneingabe-Operator (durch die beiden spitzen Klammern dargestellt), der ein Dateihandle umschließt. Dateihandles lernen Sie in Kapitel 11 genauer kennen.

3

In dieser Art von Schleife können Sie chomp nicht im Bedingungsteil benutzen. Statt dessen steht es oft als erste Anweisung im Schleifenkörper, wenn es gebraucht wird. Beispiele hierfür finden Sie im folgenden Abschnitt.

4

Na gut, der Bedingungsteil einer for-Schleife ist eigentlich nichts anderes als ein getarnter Bedingungsteil einer while-Schleife und funktioniert dort also auch.

5

Die Idee für den Namen Diamantoperator stammt von Larrys Tochter Heidi, als Randal eines Tages bei Larry vorbeikam, um ihm seine neuen Lehrmaterialien zu zeigen, und sich beschwerte, daß es keinen Namen für »dieses Ding« gab. Larry hatte selbst auch noch keinen Namen dafür, aber seine Tochter Heidi (damals acht Jahre alt) hatte die zündende Idee: »Das ist ein Diamant, Papa.« Also blieb es dabei. Danke, Heidi!

6

Aber nicht nur auf Unix-Systemen. Viele andere Systeme haben diese Art der Übergabe von aufrufenden Argumenten übernommen.

7

Immer wenn ein Programm gestartet wird, erhält es vom aufrufenden Programm eine Liste von null oder mehr aufrufenden Argumenten. Oft ist dies die Shell, die diese Liste abhängig von Ihren Eingaben zusammenstellt. Später werden wir sehen, wie Sie ein Programm mit so ziemlich jedem beliebigen String als aufrufendem Argument ausführen können. Da diese Argumente oft von der Kommandozeile der Shell kommen, werden sie gelegentlich auch »Kommandozeilen-Argumente« genannt.

8

Eine Tatsache über Unix, die Ihnen vermutlich nicht so bekannt ist: Die meisten Standard-Utilities (wie cat und sed) arbeiten nach der gleichen Konvention. Ein Bindestrich steht für eine Eingabe aus dem Standardeingabekanal.

9

Um genau zu sein, hat er Perl diese Funktionalität mitgegeben, damit er seine eigenen Utilities schreiben konnte, denn die Programme verschiedener Hersteller funktionierten nicht auf jedem System gleich. Das bedeutete natürlich, daß er Perl auf jedes System portieren mußte, das er vorfand.

10

Was eine Eingabe von der Tastatur bedeuten kann, aber nicht muß.

11

Der Name, der gegenwärtig bearbeitet wird, steht übrigens in der Perl-Spezialvariable $ARGV. Hier könnte natürlich auch - anstelle eines richtigen Dateinamens stehen, wenn die Eingabe vom Standardeingabekanal kommt.

12

Wenn Sie das Spezialarray @ARGV vor der Verwendung eines zweiten Diamantoperators neu initialisieren, sind Sie auf der sicheren Seite. @ARGV werden wir im folgenden Abschnitt kennenlernen.

13

C-Programmierer fragen sich vielleicht, wie es sich mit argc (das gibt es in Perl nicht) und dem Namen des Programms verhält (den finden Sie in $0, nicht in @ARGV). Abhängig davon, wie Sie das Programm aufgerufen haben, passiert hier vielleicht auch noch etwas mehr. Vollständige Informationen darüber finden Sie in der perlrun-Manpage.

14

Wenn Sie mehr als nur eine oder zwei solcher Optionen verwenden wollen, sollten Sie mit ziemlicher Sicherheit ein Modul benutzen, um damit umzugehen. Details dazu finden Sie in den Dokumentationen für die Module Getopt::Long und Getopt::Std, die beide Teil der Standarddistribution von Perl sind.

15

Zumindest wird standardmäßig nichts verändert. Dieses Standardverhalten kann (wie so oft in Perl) geändert werden. Diese Änderungen werden den Wartungsprogrammierer mit ziemlicher Sicherheit verwirren. Vermeiden Sie sie also, es sei denn, es handelt sich um eine »quick'n'dirty«-Lösung oder einen kleinen (und hoffentlich gut kommentierten) Teil Ihres Programms. Weitere Informationen zum Standardverhalten und wie man es ändert finden Sie in der perlvar-Manpage.

16

Ja, die Leerzeichen sind ebenfalls Standardverhalten. Auch hier empfehlen wir die Lektüre der perlvar-Manpage.

17

Es ist Ihnen doch klar, daß es sich hier um eine Liste mit drei Elementen handelt, oder? Dies ist nur eine Möglichkeit, dies in Perl auszudrücken.

18

Es wurde sogar schon ein Versuch unternommen, sämtliche klassischen Unix-Utilities in Perl neu zu implementieren. Dieses Projekt (PPT, Perl Power Tools genannt) hat bereits fast alle Utilities (und die meisten Spiele) fertiggestellt, wurde bei der Reimplementierung der Shell jedoch zurückgeworfen. Dennoch war das PPT-Projekt sehr hilfreich, denn nun stehen diese Standard-Utilities auch auf Nicht-Unix-Systemen zur Verfügung.

19

Wenn wir hier »unmittelbar« sagen, so meinen wir damit, daß bei dieser Art von Funktionsaufruf zwischen dem Funktionsnamen und der öffnenden runden Klammer kein Newline-Zeichen stehen darf. Tut es das trotzdem, so vermutet Perl hinter dieser Formulierung einen Listen-Operator anstelle eines Funktionsaufrufs.

20

Funktionen, denen Null oder nur ein Argument übergeben wird, haben dieses Problem nicht.

21

In diesem Fall benutzen wir das Wort »Format« im allgemein gebräuchlichen Sinne. Perl besitzt eine Möglichkeit, Berichte zu erstellen, die ebenfalls »Format« heißt. Wir werden aber bis zu Anhang B nicht weiter darauf eingehen (außer in dieser Fußnote) und auch da nur, um zu sagen, daß Formate nicht weiter behandelt werden. Sie sind also auf sich selbst gestellt. Weiterführende Informationen dazu finden Sie u. a. in der perlform-Manpage.

22

»Allgemeine« (»general») numerische Konversion. Oder vielleicht eine »gute Konversion für diese Zahl«.

23

Für hexadezimale und oktale Zahlen gibt es außerdem %x bzw. %o. Wir benutzen »dezimal« hier als Gedächtnisstütze: %d steht für einen ganzzahligen Dezimalwert.

24

Vielleicht dachten Sie, Sie könnten einfach einen Backslash vor dem Prozentzeichen benutzen. Netter Versuch, aber falsch. Der Grund besteht darin, daß es sich bei der Formatangabe um einen Ausdruck handelt; und der Ausdruck "\%" bezeichnet nunmal den aus einem Zeichen bestehenden String '%'. Und selbst wenn wir im Formatstring einen Backslash stehen hätten, wüßte printf nicht, was es damit anfangen soll. Abgesehen davon sind C-Programmierer es gewohnt, printf auf diese Weise zu benutzen.


TOC PREV Übungen NEXT INDEX

Copyright © 2002 by O'Reilly Verlag GmbH & Co.KG