Woche 1

# Tag 6

#
Bedingungen und Schleifen

Mit Hilfe von Bedingungen und Schleifen steuern Sie die Ausführung von Anweisungsblöcken in Perl-Skripten. Ohne solche Strukturen würde Ihr Perl-Skript einfach von oben nach unten durchlaufen und nacheinander alle Anweisungen abarbeiten, bis es zu Ende ist. Sie könnten nicht überprüfen, ob etwas einen bestimmten Wert hat, und dann gegebenenfalls zu einem anderen Teil des Codes abzweigen, Sie könnten nicht festlegen, ob und wie oft ein Anweisungsblock noch einmal ausgeführt wird - ohne Kontrollstrukturen wäre Perl sehr langweilig.

In dieser Lektion behandeln wir die verschiedenen Bedingungs- und Schleifenkonstrukte, die Sie für Ihre Perl-Skripts brauchen. Unsere heutigen Themen sind:

- Eine Einführung in Anweisungsblöcke
- Die Bedingungen `if`, `if`...`else`, `if`...`elsif` und `unless`...
- Die Schleifen `while`, `do`...`while` und `until`
- Die Schleifen `for` und `foreach`
- Die Steuerung von Schleifen mit `next`, `last`, `redo` und Labels
- Die Verwendung der Spezialvariablen `$_` als Abkürzung für viele Operationen
- Mit `<>` aus Dateien lesen

## Komplexe Anweisungen und Blöcke

Bedingungen und Schleifen werden manchmal auch komplexe Anweisungen genannt, und zwar deshalb, weil Bedingungen und Schleifen im Vergleich zu einzelnen Anweisungen, die mit einem Semikolon enden (wie zum Beispiel `$x = 5;` oder `$array[0] = "erstes";)`, wie soll ich sagen ... eben komplexer sind. Der wahrscheinlich bedeutendste Unterschied zwischen einfachen und komplexen Anweisungen ist allerdings, dass letztere mit ganzen Blöcken von Perl-Code arbeiten.

Ein Block ist einfach eine in geschweifte Klammern `{}` gesetzte Folge von beliebigen Perl-Anweisungen. Innerhalb eines Blocks können einfache Anweisungen oder andere Blöcke stehen, also alles, was auch außerhalb des Blocks auftauchen kann. Wie Anweisungen in einem Skript werden die Anweisungen in einem Block nacheinander ausgeführt. Zum Beispiel:

    while (Bedingung) { # Beginn des Blocks
       Anweisung;
       Anweisung;
       if (Bedingung) { # Beginn des if-Blocks
          Anweisung;
       }                          # Ende des if-Blocks
       # ... weitere Anweisungen
    }                             # Ende des Blocks

Eine andere Eigenschaft von Blöcken ist, dass Perl hinter der letzten Anweisung im Block nicht unbedingt ein Semikolon verlangt. Es ist aber eine gute Idee, das Semikolon trotzdem zu setzen - dann müssen Sie nicht mehr darauf achten, wenn Sie später weitere Anweisungen hinzufügen.

Blöcke, die nicht an eine Bedingung oder Schleife gebunden sind, werden bare blocks oder freistehende Blöcke genannt und nur einmal ausgeführt. Bare blocks können durchaus nützlich sein, besonders wenn Sie sie mit einem Label versehen, doch jetzt werden wir uns erst einmal auf die Blöcke konzentrieren, die zu komplexen Anweisungen gehören.

## Bedingungen

Mit Hilfe von Bedingungsanweisungen können Sie festlegen, welche Blöcke Perl ausführen soll, wenn ein Ausdruck einen bestimmten Wert hat. Sie setzen eine Bedingung auf, und Perl überprüft, ob diese Bedingung erfüllt ist, das heißt, ob der Bedingungsausdruck wahr ist. Wenn der Ausdruck wahr ist, führt Perl den direkt folgenden Block aus, wenn er falsch ist, überspringt Perl diesen Block und macht beim nächsten Block oder Teil des Skripts weiter. Anders als bei Schleifen wird jeder Block nur einmal ausgeführt.

### if, if...else, und if...elsif

Die häufigste Formen der Bedingung sind `if` (wenn) und seine Varianten `if...else` (wenn...sonst) und `if...elsif` (wenn...sonst wenn). Die `if`-Anweisung sieht wie folgt aus:

    if ( Bedingungsausdruck) {
       # Anweisungen
    }

`Bedingungsausdruck` kann jeder beliebige Ausdruck sein; er wird in Booleschem skalaren Kontext auf seinen Wahrheitswert überprüft. Sie erinnern sich, dass alles außer `""`, `0` und `undef` als wahr angesehen wird. Ist der `Bedingung`sausdruck wahr, wird der Anweisungsblock ausgeführt. Ist er falsch, passiert gar nichts, und Perl macht mit der nächsten Anweisung unter dem `if`-Block weiter.

Beachten Sie, dass anders als in C oder Java auf die Bedingung (auch bei `else` und `elsif`) immer ein Block folgen muss, auch wenn er nur eine einzige Anweisung enthält. Sie müssen die geschweiften Klammern immer setzen - aber nicht unbedingt in verschiedenen Zeilen, wie ich es eben gezeigt habe. Wo die Klammern stehen, bleibt Ihrem Geschmack überlassen.

Um einen alternativen Block ausführen zu lassen, wenn die Bedingung nicht erfüllt ist, verwenden Sie `if`...`else`:

    if ( Bedingungsausdruck ) {
       # Anweisungen, die ausgefuehrt werden,
       # wenn Bedingungsausdruck wahr ist
    } else {
       # Anweisungen, die ausgefuehrt werden,
       # wenn Bedingungsausdruck falsch ist
    }

Wie in anderen Sprachen auch, können Sie `if` und `else` auch ineinander verschachteln:

    if ( Bedingungsausdruck 1 ) {
       # Anweisung 1
    } else {
       if ( Bedingungsausdruck 2 ) {
          # Anweisung 2
       } else {
          if ( Bedingungsausdruck 3 ) {
             # Anweisung 3
          } else {
             # und so weiter...
          }
       }
    }

Um Ihnen etwas Tipparbeit zu ersparen oder unübersichtlich viele Einzüge zu vermeiden, bietet Perl eine dritte Form von `if`-Bedingungen, das `elsif`, das diese Art von Operationen um einiges kompakter macht:

    if ( Bedingungsausdruck 1 ) {
       # Anweisung 1
    } elsif ( Bedingungsausdruck 2 ) {
       # Anweisung 2
    } elsif ( Bedingungsausdruck 3 ) {
       # Anweisung 3
    } else (
       # Anweisung fuer alle anderen Faelle
    }

Beachten Sie, dass sobald die Bedingung eines `elsif` wahr ist, alle noch folgenden `elsif`-Blöcke`` übersprungen, das heißt nicht einmal ausgewertet werden. Bei verschachtelten `if...else`-Anweisungen ist es ebenso: In jeder Folge von Blöcken wird jeweils nur der erste ausgeführt, dessen Bedingung erfüllt ist.

Was ist mit Fallunterscheidungskonstrukten wie `switch` oder `case`? Perl hat von sich aus keine Anweisung für `switch` (das ist wahrscheinlich das einzige Konstrukt, für das andere Sprachen eine eigene Anweisung haben, Perl aber nicht). Allerdings gibt es verschiedene Möglichkeiten, mit vorhandenen Perl-Konstrukten eine `switch`- Anweisung zu »simulieren«. Ein paar davon werden wir im Abschnitt »Vertiefung« am Ende dieser Lektion durchgehen.

### unless

Die `unless`-Anweisung ist eine Art umgekehrtes `if`. Manchmal soll eine Operation nur ausgeführt werden, wenn eine Bedingung nicht erfüllt ist - was bedeutet, dass in einer normalen `if...else`-Anweisung all das gute Zeug in den `else`-Teil wandern würde wie hier:

    if ( Bedingungsausdruck ) {
       # mache nichts
    } else {
       # mache das hier
    }

Das ist nicht unbedingt die optimale Lösung. Sie könnten natürlich den Bedingungsausdruck negieren (mit `not` oder `!`):

    if (not Bedingungsausdruck ) {
       # mache das hier
    }

So müßten Sie es in anderen Sprachen machen und Ihre Denkweise an die Syntax anpassen. Perl aber zieht es vor, wenn Sie so denken, wie Sie zu denken gewohnt sind, und bietet Ihnen deshalb eine Alternative. Wenn Sie denken: »Mache das und das, außer unter dieser Bedingung«, also eine Operation nur ausführen möchten, wenn diese Bedingung nicht erfüllt ist, nehmen Sie einfach `unless` (vom Englischen unless, wenn nicht oder außer wenn):

    unless ( Bedingungsausdruck ) {
       # mache das hier
    }

Mit diesem `unless` wird der Block nur dann ausgeführt, wenn der `Bedingungsausdruck` falsch ist. Ist er aber wahr, wird der Block übersprungen. Sie können einem `unless` auch ein `else` hinzufügen, wenn Sie möchten (aber kein `elsif`, und so etwas wie `elsunless` gibt es zum Glück auch nicht).

### Der Bedingungsoperator

Manche Bedingungen sind so kurz, dass es viel zuviel Aufwand wäre, all diese Klammern und Worte auf sie zu verschwenden. Manchmal ist es sinnvoll, eine Bedingung in einen anderen Ausdruck zu betten (was mit `if` oder `unless` nicht möglich ist, weil diese keine Werte zurückliefern). Verwenden Sie in solchen Situationen den Bedingungsoperator ?...:.... Wie bei `if...else` brauchen Sie einen Bedingungsausdruck und zwei Angaben - was Perl bei erfüllter und bei nicht erfüllter Bedingung tun soll. Das Ganze schreiben Sie dann in folgender Form:

    Bedingungsausdruck ? wert_wenn_wahr : wert_wenn_falsch;

Hier wird der Bedingungsausdruck auf seine Wahrheit überprüft, genau wie bei `if`, und wenn er wahr ist, dann wird der Ausdruck `wert_wenn_wahr` ausgewertet (und der Wert zurückgegeben), anderenfalls wird wert_wenn_falsch ausgewertet und zurückgegeben. Anders als bei `if` und `unless` sind wert_wenn_wahr und wert_wenn_falsch einzelne Ausdrücke, keine Blöcke. Zum Beispiel können Sie mit der folgenden Zeile ganz schnell den höheren von zwei Werten herausfinden:

    $max = $x > $y ? $x : $y;

Dieser Ausdruck sieht nach, ob der Wert von `$x` größer ist als der von `$y`. Wenn ja, gibt er `$x` zurück. Wenn `$x` kleiner oder gleich `$y` ist, gibt er `$y` zurück. Der zurückgegebene Wert wird dann der Variablen `$max` zugewiesen. Mit `if` und `else` würde der gleiche Vorgang so aussehen:

    if ($x > $y) {
       $max = $x;
    } else {
       $max = $y;
    }

> Der Bedingungsoperator wird manchmal auch triadischer Operator genannt, weil er drei Operanden hat (monadische Operatoren haben einen, dyadische zwei und triadische drei Operanden).

### Kontrollflußsteuerung mit logischen Operatoren

Am Tag 2 habe ich Ihnen Perls logische Operatoren `&&`, `||`, `and` und `or` erklärt und kurz erwähnt, dass Sie damit Bedingungsanweisungen konstruieren können. Lassen Sie uns diese Operatoren hier noch einmal betrachten, damit Sie ein Gefühl dafür bekommen, wie sie arbeiten. Nehmen Sie den folgenden Ausdruck:

    $wert = $dies || $das;

Um zu verstehen, wie dieser Ausdruck funktioniert, müssen Sie sich an zwei Eigenschaften von logischen Operatoren erinnern: dass sie »kurzschließen« und dass sie den Wert des zuletzt ausgewerteten Ausdrucks zurückgeben. So wird in dem obigen Beispiel zuerst `$dies`, der linke Operand von `||`, überprüft, und dann gibt es drei Möglichkeiten:

- Wenn `$dies` irgendeinen anderen Wert als `0` oder `""` hat, ist der Ausdruck wahr. Weil der `||`-Operator kurzschließt, wird `$das` gar nicht mehr ausgewertet. Der Operator gibt das Ergebnis der letzten Auswertung zurück - den Wert von `$dies`, der dann der Variablen `$wert` zugewiesen wird.
- Hat `$dies` den Wert `0` oder `""`, ist der Ausdruck falsch. Perl wertet dann `$das` aus. Wenn `$das` wahr ist, ist der gesamte Ausdruck wahr, und der Operator gibt den zuletzt ermittelten Wert zurück - `$wert` erhält den Wert von `$das`.
- Wenn alle beide, `$dies` und `$das`, falsch sind, gibt der Operator falsch zurück, und `$wert` erhält einen `0`- oder `""`-Wert.

Mit `if`...`else` könnten Sie diese Anweisung wie folgt schreiben:

    if ($dies) { $wert = $dies; }
    else {$wert = $das; }

Mit dem Bedingungsoperator könnten Sie schreiben:

    $wert = $dies ? $dies : $das

Aber beide brauchen mehr Platz und sind schwieriger zu begreifen - zumindest schwieriger als der logische Ausdruck, der sich fast wie Klartext liest: dies oder das. Und schon sind Sie fertig.

Wie ich am Tag 2 schon erwähnt habe, werden Ihnen solche für eine Entscheidung eingesetzten logischen Operatoren am häufigsten beim Öffnen von Dateien begegnen:

    open(DATEI, "Dateiname") or die "Kann Datei nicht oeffnen\n";

Wenn die Funktion `open` eine Datei erfolgreich geöffnet hat, gibt sie wahr zurück, und deswegen wird der Teil nach dem `or` übersprungen, die Funktion `die` (»stirb«) also gar nicht erst ausgeführt. Mehr hierzu am Tag 15.

## while-Schleifen

Mit den verschiedenen `if`-Anweisungen in Perl steuert man den Programmfluß, indem man (je nachdem, ob eine Bedingung erfüllt ist oder nicht) zu verschiedenen Teilen des Skripts abzweigt. Die zweite Steuerungsmöglichkeit sind Schleifen - die ein und denselben Anweisungsblock immer wieder ausführen und erst dann aufhören, wenn eine bestimmte Bedingung erfüllt ist. Perl hat zwei generelle Arten von Schleifen, die beide ungefähr dasselbe machen: `while`-Schleifen laufen solange, bis eine Bedingung erfüllt ist, bei `for`-Schleifen ist die Anzahl der Durchläufe von Anfang an festgelegt. Man kann jede `while`-Schleife auch als `for`-Schleife formulieren und umgekehrt, doch paßt je nach Situation meist die eine besser als die andere.

Wir beginnen mit den `while`-Schleifen. Davon hat Perl drei verschiedene: `while`, `do`...`while` und `until`.

### while

Die Grundform einer Schleife in Perl ist die `while`-Schleife`` mit einem Bedingungsausdruck und einem Anweisungsblock:

    while ( Bedingungsausdruck ) {
       # zu wiederholende Anweisungen
    }

In der `while`-Schleife wird der Bedingungsausdruck ausgewertet, und wenn er wahr ist, werden die Anweisungen im Block ausgeführt. Danach wird der Bedingungsausdruck wieder ausgewertet, und wenn er immer noch wahr ist, wird der Anweisungsblock wieder ausgeführt. Dieser Vorgang wiederholt sich so lange, bis der Bedingungsausdruck falsch ergibt. Als Beispiel zeige ich Ihnen noch einmal die `while`- Schleife aus dem Krümelmonster-Skript, das Sie bereits am ersten Tag gesehen haben:

    while ( $kekse ne "KEKSE") {
       print 'ich will KEKSE: ';
       chomp($kekse = <STDIN>);
    }

Hier wiederholt sich der Eingabevorgang (die `print-` und die `<STDIN>`-Anweisung) so lange, bis die Eingabe den String `"KEKSE"` enthält. Sie könnten diese Schleifenanweisung auch so lesen: »Solange der Wert von `$kekse` nicht gleich dem String `"KEKSE"` ist, tue folgendes.«

Hier ein anderes Beispiel von Tag 4, das mit einer temporären Variablen `$i` für den Array-Index ein Array durchläuft:

    $i = 0;
    while ($i <= $#array) {
       print $array[$i++], "\n";
    }

In diesem Fall ist die Bedingung, dass `$i` kleiner oder gleich dem größten Array-Index ist. Innerhalb des Schleifenblocks geben wir das aktuelle Array-Element aus und inkrementieren `$i`. So bestimmen wir, wie oft die Schleife durchlaufen werden soll: solange wie `$i` kleiner oder gleich dem größten Index in `@array` ist, also `$#array` + 1, (weil wir ja bei 0 anfangen).

Beachten Sie beim Schreiben von `while`-Schleifen, dass innerhalb der Schleife etwas passieren muss, dass sie näher an ihr Ende bringt. Wenn Sie hier zum Beispiel vergessen, `$i` zu erhöhen, wird die Schleifenbedingung nie erfüllt, und die Schleife läuft und läuft und läuft ohne Ende.

Schleifen, die nicht enden, werden Endlosschleifen genannt und können - wenn man sie bewußt verwendet - durchaus von Nutzen sein. Zum Beispiel ist eine `while`- Schleife ohne Bedingung eine absichtlich endlose Schleife. Sie haben ein paar davon in den bisherigen Beispielen gesehen. Diese hier ist aus den Statistikskripten:

    while () {
       print 'Bitte eine Zahl eingeben: ';
       chomp ($input = <STDIN>);
       if ($input ne '') {
          $zahlen[$count] = $input;
          $count++;
          $sum += $input;
       }
      else { last; }
    }

Diese Schleife liest bei jedem Durchlauf eine Zeile von der Standardeingabe und kann niemals aufgrund einer Schleifenbedingung enden, da es keine Bedingung gibt. Doch innerhalb der Schleife überprüfen wir die Eingabe mit `if`...`else`. Wenn `$input` ein leerer String ist, unsere `if`-Bedingung also nicht erfüllt ist, wird der `else`-Block ausgeführt, der nur aus dem Schleifensteuerbefehl `last` besteht. Dieses `last` bricht die Schleife ab und geht zum nächsten Teil des Skripts weiter. Es gibt in Perl drei solcher Schleifensteuerbefehle: `last`, `next` und `redo`, die wir später in diesem Kapitel, im Abschnitt »Schleifen steuern«, genauer betrachten werden.

Ich hätte diese Schleife auch so schreiben können, dass das `while` eine richtige Bedingung hätte und zum richtigen Zeitpunkt abbrechen würde. In diesem speziellen Beispiel fand ich es aber einfacher, die Schleife eben auf diese Art zu konstruieren. Perl zwingt Ihnen auch bei Schleifen oder Bedingungen kein bestimmtes Denkmuster auf, Sie können Ihr Skript so bauen, wie Sie es für die jeweilige Aufgabe am besten halten.

### until

Genau wie `unless` das Gegenteil von `if` ist, ist `until` das Gegenteil von `while`. `Until` sieht genauso aus wie `while`, mit einem Bedingungsausdruck und einem Block:

    until ( Bedingungsausdruck ) {
       #  Anweisungen
    }

Der einzige Unterschied ist die Bedeutung der Bedingung - mit `while` wird die Schleife so lange durchlaufen, wie die der Bedingungsausdruck wahr ist. Mit `until` wird sie so lange ausgeführt, wie der Bedingungsausdruck falsch ist. Until ist englisch und heißt bis - »Bis diese Bedingung wahr ist, tue das hier.«

### do

Die dritte Form von `while`-Schleifen ist `do`. Bei `while` und `until` wird die Schleifenbedingung ausgewertet, bevor der Block ausgeführt wird - wenn der Bedingungsausdruck also von Anfang an falsch (oder bei `unless` wahr) ist, bricht die Schleife sofort ab und macht gar nichts. Manchmal aber möchten Sie einen Anweisungsblock zunächst einmal ausführen und erst danach entscheiden, wie es weitergeht. Hierfür gibt es `do`. `do`-Schleifen haben einen anderen Aufbau als `while`- und `until`-Schleifen und sehen etwa wie folgt aus (beachten Sie das Semikolon am Ende, bei `do` ist es Pflicht):

    do {
       # Schleifenblock
    } while (Bedingungsausdruck);

Dasselbe geht auch mit `until`:

    do {
       # Schleifenblock
    } until (Bedingungsausdruck);

Mit diesen beiden Anweisungen werden die Blöcke vor der Auswertung des Bedingungsausdrucks ausgeführt. Selbst wenn der Bedingungsausdruck falsch (bei `while`) oder wahr `(`bei `until`) ist, wird der Anweisungsblock mindestens einmal ausgeführt.

> In Wirklichkeit ist `do` eine Funktion, die hier nur so tut, als wäre sie eine Schleife (deshalb brauchen Sie auch das Semikolon). Meistens verhält `do` sich genau wie eine Schleife, nur wenn Sie mit Schleifensteuerbefehlen (wie `last` oder `next`) oder mit Labels (Sprungmarken) arbeiten wollen, müssen Sie statt `do` eine »echte« `while`- oder `until`-Schleife verwenden. Schleifensteuerbefehle und Labels besprechen wir noch in dieser Lektion.

## Ein Beispiel: Zahlen raten

In diesem Beispiel spielen wir ein kleines Spiel. Perl bittet Sie um eine Zahl, »zieht« dann eine Zufallszahl zwischen 1 und Ihrer Zahl, und Sie sollen raten, welche es gezogen hat, zum Beispiel:

    % zahlraten.pl
    Geben Sie die hoechste Zahl ein: 50
      Ihr Tipp? (eine Zahl zwischen 1 und 50): 25
    Zu hoch!
      Ihr Tipp? (eine Zahl zwischen 1 und 50): 10
    Zu niedrig!
      Ihr Tipp? (eine Zahl zwischen 1 und 50): 17
    Zu hoch!
      Ihr Tipp? (eine Zahl zwischen 1 und 50): 13
    Zu hoch!
      Ihr Tipp? (eine Zahl zwischen 1 und 50): 12
    Richtig!
    Gratuliere!  Sie haben mit 5 Versuchen die richtige Zahl erraten.
    %

Das Skript dazu arbeitet mit zwei endlosen `while`-Schleifen, einer Menge `if`- Bedingungen und der `rand`-Funktion zur Ermittlung einer Zufallszahl. Listing 6.1 zeigt den Code.

Listing 6.1: Das Skript zahlraten.pl

    1:  #!/usr/bin/perl -w
    2:
    3:  $top = 0;   # hoechste Zahl
    4:  $num = 0;  # Zufallszahl
    5:  $count = 0; # zaehlt Versuche
    6:  $guess = "";  # aktueller Tipp
    7:
    8:  while () {
    9:      print 'Geben Sie die hoechste Zahl ein: ';
    10:     chomp($top = <STDIN>);
    11:     if ($top == 0 || $top eq '0') {
    12:         print "Das ist keine gute Zahl.\n";
    13:     }
    14:     else { last; }
    15: }
    16:
    17: srand;
    18: $num = int(rand $top) + 1;
    19:
    20: while () {
    21:     print " Ihr Tipp? (eine Zahl zwischen 1 und $top): ";
    22:     chomp($guess = <STDIN>);
    23:     if ($guess == 0 || $guess eq '0') {
    24:         print "Das ist keine gute Zahl.\n";
    25:     } elsif ($guess < $num) {
    26:         print "Zu niedrig!\n";
    27:         $count++;
    28:     } elsif ($guess > $num) {
    29:         print "Zu hoch!\n";
    30:         $count++;
    31:     } else {
    32:         print "\a\aRichtig!  \n";
    33:         $count++;
    34:         last;
    35:     }
    36: }
    37: print "Gratuliere!  Sie haben mit $count Versuchen";
    38: print " die richtige Zahl erraten.\n";

Dieses Skript hat vier Teile: Initialisierung, Eingabe der höchsten Zahl, Auswahl der zu ratenden Zahl und dann das Raten selbst. Den Initialisierungsteil überspringe ich diesmal ganz; Sie werden mittlerweile wissen, wie man Skalarvariablen zuweist.

In Zeile 8 bis 15 erfragen wir, wie hoch die Zufallszahl höchstens sein darf. Diese `while`-Schleife ist endlos, doch die Bedingung in Zeile 11 soll sicherstellen, dass Sie nicht 0 eingeben. Tun Sie es doch, wird eben nicht das `last` im `else`-Block, sondern die Eingabeschleife von vorn ausgeführt. Denken Sie daran, dass Perl bei der Umwandlung von Strings in Zahlen einen String in 0 konvertiert, wenn es keine numerischen Daten in ihm findet. Deswegen fängt diese Bedingung sowohl eine 0 als auch alle anderen nichtnumerischen Eingaben ab. Perl-Warnungen würden sich bei der Eingabe eines Strings trotzdem über »`not a number`« (»keine Zahl«) beschweren. Wenn wir uns in ein paar Tagen mit Mustervergleichen (Pattern Matching) befassen, werden Sie einen besseren Weg kennenlernen, diese Warnungen zu umgehen.

Jedenfalls generieren wir, sobald wir eine brauchbare Zahl haben, in Zeile 17 und 18 die geheime Zahl, und zwar mit den eingebauten Funktionen `srand` und `rand`. Ohne Argumente setzt die `srand`-Funktion den Zufallsgenerator auf die aktuelle Uhrzeit, damit wir jedesmal andere Zahlen bekommen (ansonsten wäre es in der Tat ein sehr langweiliges Spiel). Die Funktion `rand` generiert dann eine Zufallszahl zwischen 0 und einem Argument, hier unserem `$top` (einschließlich 0 ohne `$top`). Wir schneiden diese Zahl auf einen Integer zu, addieren `1`, damit wir keine Nullen, aber gelegentlich auch die eingegebene Höchstzahl erhalten, und speichern sie in der Variablen `$num`.

In den Zeilen 20 bis 34 wird geraten. Hier überprüfen wir mit `if`...`elsif`- Anweisungen drei Dinge:

- Ob der Vorschlag eine 0 ist (Zeile 23); in diesem Fall geben wir eine Warnung aus.
- Ob der Vorschlag kleiner als die geheime Zahl ist; in diesem Fall geben wir »Zu niedrig!« aus.
- Ob der Vorschlag größer als die geheime Zahl ist; in diesem Fall geben wir »Zu hoch!« aus.

Wenn der Vorschlag weder zu niedrig noch zu hoch, aber eine gültige Zahl ist, dann muss es die richtige Zahl sein - also piepen wir zweimal (okay, Sie vielleicht nicht, aber die Escape-Sequenz `\a` gibt einen Piepton aus), Perl schreibt einen Glückwünsch auf den Bildschirm und hört auf.

Während der Benutzer Zahlen rät, merken wir uns die Anzahl der Versuche in einer `$count`-Variablen. Wir wollen aber nur gültige Versuche zählen, also inkrementieren wir `$count` nur in den drei Fällen gültiger Zahlen (zu niedrig, zu hoch, richtig). `$count` wird dann mit der Glückwunschmeldung ausgegeben.

## for-Schleifen

Geht es um die Wiederholung eines Anweisungsblocks, bietet `while` als »Mutter aller Schleifen« immer einen Weg - sie führt einen Block immer wieder aus, und zwar so lange (bzw. bis) die Schleifenbedingung erfüllt ist. Die zweite Art von Schleife, die `for`- Schleife, löst das gleiche Problem auf etwas andere Art und Weise. Bei `for`-Schleifen wird der Anweisungsblock n-mal hintereinander ausgeführt, dann wird die Schleife beendet.

Sie könnten jede `while`-Schleife auch als `for`-Schleife formulieren und umgekehrt. Aber für manche Aufgaben bietet sich die eine Form eher an als die andere.

Perl kennt zwei `for`-Schleifen: eine allgemeine C-ähnliche `for`-Schleife und eine dem Shell-Skripting entliehene `foreach`-Schleife, die einen Block »für jedes« (for each) Element einer Liste wiederholt.

### for

Die `for`-Schleife ist in Perl dieselbe wie in C. Sie setzen eine Lauf- oder Zählervariable (zum Beispiel `$i)` auf einen beliebigen Startwert, überprüfen eine Bedingung, führen den Schleifenblock aus, wenn diese erfüllt ist, ändern dann den Wert der Laufvariablen, überprüfen wieder die Bedingung, durchlaufen die Schleife gegebenenfalls von neuem, ändern wieder die Laufvariable - und so weiter:

    for ( Startwert; Bedingungsausdruck; Aenderung ) {
       # Anweisungen
    }

In diesem Beispiel ist `Startwert` der Ausdruck zum Initialisieren der Laufvariablen, der `Bedingungsausdruck` entscheidet, ob die Iteration weitergeht, und `Aenderung` bestimmt, wie der Wert der Laufvariablen nach jedem Schleifendurchlauf geändert wird. Beim ersten Durchlauf wird der Zähler initialisiert, die Bedingung ausgewertet und, wenn sie wahr liefert, der Anweisungsblock ausgeführt. Beim zweiten Durchlauf wird die Änderungsanweisung ausgeführt, die Bedingung wieder überprüft und wenn sie immer noch wahr ist, der Block wieder ausgeführt. So geht es immer weiter, bis der Bedingungsausdruck ein falsch zurückgibt.

Für fünf Schleifendurchläufe könnten Sie zum Beispiel eine `for`-Schleife wie diese verwenden:

    for ( $i = 1; $i <= 5; $i++) {
       print "Durchlauf $i\n";
    }

Dieser Code-Schnipsel produziert folgende Ausgabe:

    Durchlauf 1
    Durchlauf 2
    Durchlauf 3
    Durchlauf 4
    Durchlauf 5

Sie könnten diese Schleife selbstverständlich auch als `while`-Schleife schreiben:

    $i = 1;
    while ($i <= 5) {
       print "Durchlauf $i\n";
       $i++;
    }

Beide Schleifen würden fünfmal durchlaufen, doch die `for`-Schleife ist kompakter und übersichtlicher. Sie listet sozusagen fein säuberlich auf, wo sie anfängt, wo sie aufhört und welche Schritte sie dazwischen unternimmt. Für manche Aufgaben eignet sie sich besser als eine `while`-Schleife - beispielsweise, wenn Sie einen begrenzten Satz von Werten durchgehen, etwa zum Bearbeiten aller Elemente in einem Array.

Sie können die Initialisierung, den Bedingungsausdruck oder die Änderungsanweisung oder alle drei im oberen Teil der `for`-Schleife weglassen, nicht jedoch die Semikola. Eine endlose `for-`Schleife könnte zum Beispiel folgendermaßen aussehen:

    for ($i = 0; ; $i++) {
       # Anweisungen
    }

Oder einfach so:

    for (;;) {
       # Anweisungen
    }

Die erste Schleife initialisiert den Schleifenzähler `$i` und erhöht ihn nach jedem Durchlauf um 1. Die zweite Schleife hat nicht einmal eine Zählervariable, sie wird einfach nur durchlaufen. Um aus diesen Schleifen herauszukommen, müssen Sie die Abbruchbedingung innerhalb der Blöcke festlegen.

Sie wollen die Schleife über mehrere Zähler laufen lassen? Setzen Sie einfach Kommas zwischen Ihre Zählerausdrücke:

    for ($i=0, $j=1; $i < 10, j$ < $total; $i++, $j++ ) {
       # Anweisungen
    }

In diesem Fall wird die Schleife abgebrochen, wenn einer der beiden Bedingungsausdrücke wahr zurückgibt.

### foreach

Die `for`-Schleife bietet sich an, wenn Sie auf einen konkreten Endpunkt überprüfen können - zum Beispiel den höchsten Index in einem Array oder eine bestimmte Zahl. `foreach` ist eine Kurzversion von `for` ohne expliziten Zähler. `foreach` durchläuft eine Liste und führt den Anweisungsblock so viele Male aus, wie diese Liste Elemente hat.

Sie kennen `foreach` bereits - gestern haben wir uns damit durch Listen von Hash- Schlüsseln gearbeitet. Hier nun die ausführliche »Gebrauchsanweisung« für `foreach`: Sie übergeben der `foreach`-Schleife eine Liste - zum Beispiel eine Liste aller Schlüssel in einem Hash oder einen Bereich. `foreach` durchläuft die Liste, setzt dabei eine temporäre Variable nacheinander auf den Wert jedes Elements und führt für jedes Element in der Liste den Anweisungsblock aus.

Hier ein simples Äquivalent zur `for`-Schleife von vorhin, die den Zähler fünfmal ausgegeben hat:

    foreach $zahl (1 .. 5) {
       print "Durchlauf $zahl\n";
    }

Hier erstellt der Bereichsoperator `..` eine Liste der Zahlen 1 bis 5, und die `foreach`- Schleife arbeitet sich durch diese Liste, wobei sie die Zahlen nacheinander der Variablen `$zahl` zuweist.

`Foreach`-Schleifen eignen sich außerordentlich gut zum Durchlaufen von Listen, zum Beispiel um die Schlüssel und Werte eines Hash auszugeben, wie Sie gestern gelernt haben:

    foreach $key (sort keys %hashname) {
       print "Schlüssel: $key Wert: $hashname{$key}\n";
    }

Wie Sie sehen, habe ich die `$key`-Variablen hier nicht initialisiert (auch die `$zahl`- Variable im vorangehenden Beispiel wurde nicht initialisiert). Das ist nicht nötig, weil die Variable zwischen `foreach` und der Liste eine lokale Variable innerhalb der Schleife ist - das heißt, vor und nach der Schleife existiert sie gar nicht. Falls sie doch schon existiert, Sie also vor der Schleife eine gleichnamige Variable verwenden, benutzt `foreach` sie nur vorübergehend und stellt ihren ursprünglichen Wert wieder her, sobald die Schleife verlassen wird. Sie können sich die `foreach`-Variable als eine Art »Kritzel«-Variable vorstellen, die einzig und allein dem kurzen Speichern von Elementen dient und beim Schleifenende weggeworfen wird.

Wenn Sie die Werte der Listenelemente gar nicht brauchen, sondern lediglich die Liste durchgehen möchten, können Sie diese Variable auch einfach weglassen.

## Schleifen steuern

In w`hile`- und `for`-Schleifen stehen die Bedingungen, unter denen die Schleife durchlaufen bzw. abgebrochen werden soll, ganz am Anfang. In vielen Fällen ist das auch genau das Richtige. Wenn Sie aber mit komplexeren Schleifen arbeiten oder mit Endlosschleifen spielen, wie ich es in einigen der bisherigen Beispiele getan habe, möchten Sie vielleicht auch mitten in der Schleife entscheiden können, wie es von diesem Punkt aus weitergehen soll. Und natürlich hat Perl da etwas für Sie: Schleifensteuerbefehle.

> Wie ich schon erwähnt habe, können Sie in `do`-Schleifen keine Schleifensteuerbefehle oder Labels (Sprungmarken) einsetzen. Dafür müßten Sie die `do`-Schleife zu einer `while`- , `until`- , `for`- oder `foreach`-Schleife umschreiben.

Mit Schleifensteuerbefehlen steuern Sie den Ablauf einer Schleife. Zwei von ihnen haben Sie bereits gesehen: `next` und `last`, um die Schleife neu zu starten oder sie komplett abzubrechen. Außerdem gibt es in Perl `redo` und Labels, mit denen Sie Ihren Schleifen einen Namen geben können.

### last, next und redo

Die Grundbausteine der Schleifensteuerung sind die Schlüsselwörter `last`, `next` und `redo`. Sobald eins von ihnen in einer `while`- oder `for`-Schleife auftritt, unterbricht Perl die normale Schleifenausführung.

Sie können jeden dieser drei Befehle mit einem Label oder für sich allein verwenden. Mit einem Label beziehen sie sich auf die mit diesem Label versehene Schleife ohne Label auf die innerste Schleife (mehr über Labels später). Im einzelnen bewirken sie folgendes:

- Mit `last` wird die Schleife sofort abgebrochen und verlassen (wie bei `break` in C). Der nächste Teil des Skripts wird ausgeführt.
- Mit `next` wird der aktuelle Durchlauf abgebrochen, es wird zurück zum Schleifenanfang gesprungen, und der nächste Durchlauf wird mit der Überprüfung der Bedingung gestartet. Die Anweisung entspricht dem `continue`- Befehl in C. Das Schlüsselwort `next` ist ein bequemer Weg, unter bestimmten Bedingungen den Code im Rest des Anweisungsblocks zu überspringen.
- bei `redo` wird der aktuelle Schleifendurchlauf abgebrochen und wieder an den Anfang gegangen. Der Unterschied zwischen `redo` und `next` ist, dass `next` den Bedingungsausdruck am Schleifenanfang noch einmal auswertet (und bei `for`- Schleifen die Änderungsanweisung ausführt). `Redo` hingegen startet erst am Anfang des Blocks neu, ohne den Bedingungsausdruck am Schleifenanfang noch einmal auszuwerten. Sie können sich den Unterschied so vorstellen, dass `redo` denselben Durchlauf noch einmal wiederholt, während `next` zum nächsten springt (und das ist tatsächlich exakt das, was passiert).

Schauen wir uns noch einmal das Zahlenratespiel an. Wir könnten die zweite `while`- Schleife auch so schreiben:

    while () {
        print "Ihr Tipp? (eine Zahl zwischen 1 und $top): ";
        chomp($guess = <STDIN>);
        if ($guess == 0 || $guess eq '0') { # wenn Eingabe 0 oder String
            print "Das ist keine gute Zahl.\n";
            next;
        }
        if ($guess < $num)  { # wenn Eingabe zu niedrig
            print "Zu niedrig!\n";
            $count++;
            next;
        }
        if ($guess > $num) { # wenn Eingabe zu hoch
            print "Zu hoch!\n";
            $count++;
            next;
        }
        $count++;
        last;
    }

Weil diese `while`-Schleife endlos ist, müssen wir mit Schleifensteuerbefehlen innerhalb des Schleifenkörpers festlegen, wann sie abgebrochen werden soll - in unserem Fall, wenn die richtige Zahl eingegeben wurde. In dem Schleifenblock wird die Eingabe auf drei verschiedene Dinge überprüft: ob sie gleich 0 ist, ob sie kleiner als die geheime Zahl ist oder ob sie größer als die geheime Zahl ist. In jeder dieser `if`-Anweisungen überspringen wir, wenn eins dieser Kriterien zutrifft, mit `next` sofort die restlichen Anweisungen im Block und gehen zurück zum Schleifenanfang (stünde dort ein Bedingungsausdruck, würden wir ihn jetzt aufs neue auswerten). Wenn die eingegebene Zahl allen drei Überprüfungen standhält, dann ist es die richtige, und wir können die Schleife mit `last` abbrechen.

Im allgemeinen sind Schleifensteuerbefehle nur notwendig, wenn Sie innerhalb der Schleife auf bestimmte Bedingungen überprüfen, die den normalen Ablauf der Schleife unterbrechen sollen, oder um aus einer Endlosschleife herauszukommen.

### Labels

Schleifensteuerbefehle ohne Labels beziehen sich auf die innerste Schleife, das heißt, sie unterbrechen die sie unmittelbar umschließende Schleife. Manchmal haben Sie aber mehrere ineinandergeschachtelte Schleifen und möchten unter einer bestimmten Bedingung aus mehreren Schleifen aussteigen. Aus diesem Grund können Sie Schleifen mit Labels kennzeichnen und dann mit `last`, `next` und `redo` zu diesen Schleifen springen.

Labels stehen am Anfang der Schleife und werden vereinbarungsgemäß großgeschrieben, damit man sie nicht mit Perl-Schlüsselwörtern durcheinanderbringt. Verwenden Sie einen Doppelpunkt, um das Label von der Schleife zu trennen:

    LABEL: while (Bedingungsausdruck) {
       #...
    }

Innerhalb der Schleife verwenden Sie dann `last`, `next` oder `redo` mit dem Label:

    LABEL: while (Bedingung_1) {
       #...
       while (Bedingung_2) {
          # ...
          if (noch_eine_Bedingung) {
             last LABEL;
          }
       }
    }

Ihre Labels können Sie nennen, wie Sie wollen, mit zwei Ausnahmen: `BEGIN` und `END` sind für die Paketkonstruktion und -dekonstruktion reserviert. Mit Paketen (englisch Packages) werden wir uns in diesem Buch nicht befassen; lediglich am Tag 13 werde ich Ihnen erklären, was ein Paket überhaupt ist. Wenn Sie bereit sind, in fortgeschrittene Perl-Konzepte einzusteigen, schauen Sie in der perlmod-Manpage nach weitergehenden Informationen über Pakete und Module.

Hier ein einfaches Beispiel ohne Labels. Die äußere `while`-Schleife überprüft, ob die Variable `$exit` ungleich dem String `'n'` ist. Die innere `while`-Schleife ist eine Endlosschleife.

    while ($exit ne 'n') {
       while () {
            print 'Geben Sie eine Zahl ein: ';
            chomp($zahl = <STDIN>);
            unless ($zahl eq '0' ) { # die Zahl 0 ist erlaubt
               if ($zahl == 0 ) {    # wenn Eingabe ein String
                  print "Keine Strings.  Zahlen von 0 bis 9, bitte.\n";
                  next;
               }
            }
            # andere Anweisungen
            last;
        }
        print 'Eine weitere Zahl ausprobieren (j/n)?: ';
        chomp ($exit = <STDIN>);
    }

In diesem Beispiel verlassen `next` und `last` die sie unmittelbar einschließende Schleife - also die innere, endlose Schleife, in der Sie die Zahl eingeben. Die äußere Schleife wird nur in einem einzigen Fall verlassen: wenn Sie `$exit` am Ende der äußeren Schleife auf `'n'` setzen.

Sagen wir, Sie wollten diesem Skript noch die Möglichkeit hinzufügen, dass man mit der Eingabe von `exit` alle Schleifen und das Skript beenden kann. Dazu würden Sie vor die äußere Schleife ein Label setzen:

    AUSSEN: while ($exit ne 'n') {
       # etc.
    }

In der inneren Schleife würden Sie dann `last` mit dem Label verwenden:

    AUSSEN: while ($exit ne 'n') {
       while () {
            print 'Geben Sie eine Zahl ein (Beenden mit exit): ';
            chomp($zahl = <STDIN>);
            if ($zahl eq "exit" ) { # schluss, aus, vorbei!
               last AUSSEN;
            }
            # etc.
        }
     #  mehr...
    }

Wenn der Benutzer hier am `'Geben Sie eine Zahl ein'`- Prompt »exit« eintippt, bezieht sich der `last`-Befehl auf die Schleife mit dem Label `AUSSEN` - beendet also in diesem Fall die äußere Schleife.

Beachten Sie, dass Schleifensteuerbefehle sich auf Schleifen beziehen und nicht auf eine bestimmte Position im Skript (wie etwa `goto`, falls Ihnen das etwas sagt). Am besten stellt man sich die Labels weniger als Sprungmarken, als vielmehr als Bezeichner für Schleifen, die man anspringen kann, vor. Wenn Sie mit einem Schleifensteuerbefehl aus einer Schleife herausspringen, springen Sie in Wirklichkeit nicht zu dem Label, sondern zur Anweisung direkt hinter der Schleife mit dem Label.

## Die Variable $_

Glückwunsch! Sie wissen jetzt so gut wie alles, was Sie über Schleifen in Perl wissen müssen (das übrige erfahren Sie im Abschnitt »Vertiefung«). Weil wir in dieser Lektion noch etwas Platz haben, beende ich sie heute mit zwei allgemeinen Themen: der speziellen Variablen `$_` und einer einfachen Methode, Dateien zu lesen. Beides werden wir in den weiteren Kapiteln dieses Buches häufig gebrauchen.

Fangen wir mit der `$_`-Variablen an. Diese spezielle Variable können Sie sich als einen Standardplatzhalter für skalare Werte vorstellen. Viele Perl-Konstrukte verwenden `$_`, wenn Sie keine eigene Skalarvariable angeben. Sie können das ausnutzen und Ihre Skripts kürzer und effizienter machen - manchmal allerdings auf Kosten der Lesbarkeit.

Betrachten wir ein Beispiel: `foreach`. Sie erinnern sich, dass die `foreach`-Schleife mit einer temporären Variablen arbeitet, in der die Listenelemente nacheinander zwischengespeichert werden. Wenn Sie keine temporäre Variable angeben, speichert Perl die Werte in `$_` . Innerhalb der Schleife können Sie sich dann auf `$_` beziehen, um an diesen Wert heranzukommen. Statt

    foreach $key (sort keys %hash) {
       print "Schlüssel: $key Wert: $hash{$key}\n";
    }

können Sie genausogut schreiben:

    foreach (sort keys %hash) {
       print "Schlüssel: $_ Wert: $hash{$_}\n";
    }

Auch viele Funktionen arbeiten mit dem Wert von `$_` , wenn Sie ihnen keine Argumente übergeben - `print` und `chomp` zum Beispiel. Wundern Sie sich also nicht über Anweisungen wie:

    print;

Fügen Sie einfach in Gedanken das `$_` hinzu:

    print $_;

Wir werden uns die `$_`-Variable noch genauer ansehen - im nächsten Abschnitt und am Tag 9.

## Mit <> und while-Schleifen aus Dateien lesen

In den Beispielen der letzten Tage haben wir Eingabedaten mit `<STDIN>` von der Tastatur gelesen. Wir haben den Datei-Handle für die Standardeingabe sowohl in skalarem als auch in Listenkontext verwendet, und der Unterschied zwischen `<STDIN>` in skalarem Kontext (Zeile für Zeile) und Listenkontext (bis zum Dateiendezeichen) sollte Ihnen halbwegs klar sein.

Bestehen Ihre Eingabedaten allerdings aus mehr als nur ein paar Zeilen, ist es äußerst mühsam, sie alle über die Tastatur einzutippen. Das Statistikskript von gestern ist ein gutes Beispiel dafür - es dauert eine ganze Weile, alle Zahlen einzugeben, und wollten wir auch nur einen einzigen Wert hinzufügen, müßten wir komplett von vorne anfangen.

Viel praktischer wäre es, die Daten in eine eigene Datei zu speichern und von dort zu lesen, wenn das Skript ausgeführt wird. Dafür gibt es in Perl zwei Möglichkeiten: Zum einen können Sie eine bestimmte Datei innerhalb Ihres Skripts öffnen und lesen. Diesem Thema habe ich eine ganze Lektion gewidmet, nämlich Tag 15. Es gibt aber noch einen schnelleren Weg, Daten aus beliebigen Daten in ein Perl-Skript einzulesen. Diese Technik, die auf dem Perl-Befehl `line` beruht, bringe ich Ihnen heute bei.

Wir haben den Zeileneingabeoperator `<>` bis jetzt immer in Zusammenhang mit dem Datei-Handle `<STDIN>` verwendet. Wenn Sie `<>` aber ohne ein Datei-Handle einsetzen, holt Perl sich den Input aus der Datei, die Sie in der Kommandozeile angeben. Ein `<>` ohne Datei-Handle bedeutet im Grunde nichts anderes als: »Nimm alle in der Kommandozeile genannten Dateien, öffne sie, verkette sie miteinander und lies sie, als wären sie eine einzige Datei.«

> Genaugenommen entnimmt Perl die Namen der zu öffnenden und zu lesenden Dateien von einer speziellen Variablen namens `@ARGV`, einem Array mit Dateinamen oder anderen in der Kommandozeile angegebenen Werten, und Sie könnten `@ARGV` im Skript noch verändern. Aber fürs erste nehmen wir einfach an, dass `@ARGV` die in der Kommandozeile angegebenen Dateinamen enthält und `<>` mit diesen arbeitet. Mehr über `@ARGV` erfahren Sie an Tag 15.

Hier ein Beispiel, das die in der Kommandozeile angegebenen Dateien zeilenweise einliest und jede Zeile nacheinander ausgibt:

    while (defined($input = <>)) {
       print "$input";
    }

> Wenn Sie mit MacPerl arbeiten, haben Sie vielleicht gar keine Kommandozeile und wissen nicht recht, was Sie jetzt machen sollen. Aber keine Angst, es geht auch in MacPerl: Speichern Sie Ihr Skript als Droplet (diese Option finden Sie im Save-Dialog, Menü Type). Ist Ihr Skript als Droplet gespeichert, können Sie die zu lesenden Dateien per Drag & Drop auf das Skript-Icon ziehen - MacPerl wird starten und diese Dateien in das Skript lesen.

Sagen wir, Sie hätten dieses Beispiel als `echodatei.pl` gespeichert und möchten die Datei `eine_Datei.txt` ausgeben. Von der Kommandozeile rufen Sie das Skript folgendermaßen auf:

    % echodatei.pl eine_Datei.txt

Wenn Sie mehrere Dateien ausgeben wollen, hängen Sie einfach alle anderen Dateinamen hinten an die Kommandozeile an:

    % echodatei.pl eine_Datei.txt noch_eine_Datei.txt Datei_3.txt

Perl wird all diese Dateien öffnen und eine nach der anderen ausgeben.

Gehen wir diese `while`-Schleifenbedingung noch einmal von innen nach außen durch, damit Sie verstehen, was hier vor sich geht. Das `$input = <>` wird Ihnen vertraut vorkommen; es ist ähnlich wie beim Lesen einer Zeile mit `<STDIN>` in skalarem Kontext. Vielleicht erinnern Sie sich noch, dass die `defined`-Funktion wahr oder falsch zurückgibt, je nachdem, ob ein Argument definiert ist oder nicht (das heißt, nicht den undefinierten Wert enthält). Hier verwenden wir `defined`, um die Schleife beim Dateiende abzubrechen - für jede Zeile der Datei bekommen wir einen gültigen Wert, doch am Ende der Datei liefert `<>` undefiniert zurück - `$input` wird ebenfalls undefiniert, die `defined`-Funktion liefert falsch, und die `while`-Schleife wird gestoppt.

> Rein technisch gesehen brauchen Sie den `defined`-Teil gar nicht. Perl versteht auch so, wo das Dateiende ist, und hört automatisch auf zu lesen. Wenn Sie aber die Warnungen eingeschaltet haben, beschwert Perl sich, dass Sie nicht ausdrücklich auf das Dateiende geprüft haben. Sie können beides umgehen, die Warnung und den Aufruf von `defined`, wenn Sie `$_` als Eingabevariable nehmen (siehe unten).

Wie bei `<STDIN>` können die leeren spitzen Klammern sowohl in skalarem als auch im Listenkontext verwendet werden. In skalarem Kontext lesen Sie die Eingabedateien zeilenweise ein (wobei das Zeilenende ein Zeilenvorschub oder - auf dem Mac - ein Carriage Return ist). Im Listenkontext wird jede Zeile der Datei (bzw. der Dateien) als ein Element der Liste gespeichert.

Eine noch kürzere Version des `echodatei`-Skripts macht von der `$_`-Variablen Gebrauch. Sie können die Variable `$input` durch `$_` ersetzen und auf die temporäre Variable und die `defined`-Funktion komplett verzichten:

    while (<>) {
       print;
    }

Wenn die Bedingung einer `while`-Schleife nichts als einen Zeileneingabeoperator enthält, liest diese `while`-Schleife die Eingabedateien Zeile für Zeile ein, weist jede Zeile nacheinander der Variablen `$_` zu und bricht ab, wenn `<>` undefiniert ist, ohne dass Sie darauf ausdrücklich überprüfen müßten - Sie erhalten deswegen keine Fehlermeldung. Diesen Mechanismus können Sie eigentlich mit jeder Eingabequelle verwenden - es funktioniert auch mit `<STDIN>` oder einem anderen Datei-Handle.

Beachten Sie allerdings, dass es die `while`-Schleife ist - nicht der `<>`-Operator -, die die Zeilen nacheinander `$_` zuweist und auf das Dateiende überprüft. Sie können zum Beispiel nicht `do chomp(<>)` schreiben; dieser Funktionsaufruf würde die aktuelle Zeile nicht in `$_` speichern. Dieses besondere Feature haben nur `while`- und `for`-Schleifen (`for`-Schleifen werden als »verkleidete« `while`-Schleifen betrachtet).

Dieser Mechanismus ist ein sehr gebräuchlicher Weg, Daten in ein Perl-Skript einzulesen. In vielen Perl-Skripten werden Ihnen gleich am Skript-Anfang solche Schleifen begegnen, die Daten aus Dateien in ein Array oder einen Hash speichern.

Betrachten wir ein weiteres Beispiel für den Einsatz von `<>` und `$_` zum Einlesen von Daten. Gestern hatten wir ein Skript, das den Benutzer um die Eingabe von ein paar Namen bittet und diese dann in ein Array speichert. Die Eingabeschleife dieses Skripts sah wie folgt aus:

    while () {
         print 'Geben Sie einen Namen ein (Vor- und Nachname): ';
         chomp($in = <STDIN>);
         if ($in ne '') {
             ($fn, $ln) = split(' ', $in);
             $namen{$ln} = $fn;
         }
         else { last; }
    }

Jetzt möchten wir diese Namen aus einer Datei einlesen. Zu diesem Zweck streichen wir die Eingabeaufforderung, den Aufruf von `<STDIN>` und die Überprüfung auf eine leere Eingabe:

    while (defined($in = <>)) {
        chomp($in);
        ($fn, $ln) = split(" ", $in);
        $namen{$ln} = $fn;
    }

Mit Hilfe der `$_`-Variablen können wir das Skript noch weiter kürzen:

    while (<>) {
        chomp;
        ($fn, $ln) = split(' ');
        $namen{$ln} = $fn;
    }

Obwohl sie kein einziges Mal explizit auftaucht, wird die `$_`-Variable hier sehr viel verwendet: Die `while`-Schleife weist ihr eine Zeile aus der Input-Datei zu und überprüft, ob dieser Wert definiert ist. Dann greift `chomp` auf diesen Wert zu und schneidet den Zeilenvorschub ab. Und `split` sieht in `$_` nach, was es denn splitten soll. Derartige Abkürzungen sind in Perl-Skripten sehr häufig.

Wir könnten sogar die `split`-Funktion noch weiter abkürzen und das leere Argument weglassen. Ohne Argumente trennt `split` den String in `$_` an den Leerstellen:

    ($fn, $ln) = split;

## Vertiefung

Wie in den bisherigen Lektionen habe ich Ihnen auch zu Bedingungen und Schleifen noch nicht alles gesagt. Zum Teil möchte ich dies jetzt nachholen und Ihnen einige der bisher ausgelassenen Konzepte vorstellen. Ansonsten steht es Ihnen natürlich frei, auf eigene Faust weiterzuforschen.

### Modifikatoren für Bedingungen und Schleifen

Alle Bedingungen und Schleifen, die Sie heute kennengelernt haben, sind (mit Ausnahme von `do`) komplexe Anweisungen - sie operieren auf Blöcken aus anderen Anweisungen und benötigen kein Semikolon am Zeilenende. Daneben besitzt Perl auch einen Satz sogenannter Modifikatoren (Modifier) für einfache Anweisungen, mit denen Sie bedingungs- und schleifenähnliche Anweisungen erstellen können. Manchmal helfen Modifikatoren, einfache Bedingungen und Schleifen noch kürzer auszudrücken oder die Logik einer Anweisung besser darzustellen.

Es gibt vier mögliche Modifikatoren: `if`, `unless`, `while` und `until`. Jeden dieser Modifier können Sie an eine einfache Anweisung anhängen, direkt vor dem Semikolon. Hier ein paar Beispiele:

    print "$wert" if ($wert < 10);
    $z = $x / $y if ($y > 0);
    $i++ while ($i < 10);
    print $wert until ($wert++ > $maxwert)

Mit einem Bedingungsmodifikator (`if` oder `unless`) wird der vordere Teil der Anweisung nur ausgeführt, wenn der Bedingungsausdruck wahr (oder bei `unless` falsch) ist. Mit einem Schleifenmodifikator (`while` oder `until)` wird die Anweisung so lange ausgeführt, wie der Ausdruck wahr (bzw. bei `until` falsch) ist.

Beachten Sie, dass Anweisungen mit `while`- oder `until`-Modifikatoren nicht dasselbe sind wie normale `while-` oder `until`-Schleifen. Sie können sie weder mit Schleifensteuerbefehlen wie `next` oder `last` kontrollieren noch ihnen Schleifenlabels zuweisen.

Die `do`-Schleifen, die Sie vorhin in diesem Abschnitt kennengelernt haben, sind genau genommen Anweisungen mit Schleifenmodifikatoren. `Do` ist eine Funktion, die einen Anweisungsblock ausführt. Mit den Modifikatoren `while` und `until` bestimmen Sie, wie oft diese Ausführung wiederholt wird. Deshalb können Sie in `do`-Schleifen keine Schleifensteuerbefehle verwenden.

### continue-Blöcke

Ein `continue`-Block ist ein optionaler Anweisungsblock nach einer Schleife, der ausgeführt wird, wenn der Schleifenblock zu Ende ausgeführt oder die Schleife mit `next` unterbrochen wurde. Der `continue`-Block wird nicht ausgeführt, wenn Sie die Schleife mit `last` oder `redo` abbrechen. Nach der Ausführung des `continue`-Blocks macht Perl mit dem nächsten Schleifendurchlauf (also der Auswertung der Schleifenbedingung) weiter.

Sie könnten einen `continue`-Block zum Beispiel verwenden, um einen Fehler zu beheben, der die Schleife unterbrochen hat. Danach läuft die Schleife normal weiter. Das ganze sieht folgendermaßen aus (wobei die `while`-Schleife hier auch eine `for`- oder `foreach`-Schleife sein könnte):

    while ( Bedingung ) {
       # Anweisungen
       if (noch eine Bedingung) {
          # Fehler!
          next;  # (springt zum continue-Block)
       }
       # weitere Anweisungen
    } continue {
       # behebe Fehler
       # (danach geht's am Schleifenanfang weiter)
    }

> Beachten Sie, dass `continue` in Perl etwas anderes ist als das `continue` in C - dessen Perl-Pendant ist `next`.

### Konstruktion von switch- oder case-Anweisungen

Bemerkenswerterweise hat Perl keine explizite `switch`- (oder, je nach Ihrer Lieblingssprache, auch `case`-) Anweisung. Eine `switch`-Anweisung (Schalter, Weiche) ist eine Fallunterscheidung, mit der Sie einen Wert überprüfen, und viel kompakter, übersichtlicher und oft auch effizienter als eine Konstruktion aus zahllosen `if` und `elsif`, um festzulegen, was bei welchem Ergebnis geschehen soll.

Ein solches `switch`-Konstrukt können Sie in Perl jedoch »nachahmen«. Hier erweisen sich freistehende Blöcke (bare blocks) als äußerst nützlich. Für freistehende Blöcke gelten die gleichen Regeln wie für Schleifenblöcke; deswegen können Sie sie mit Labels versehen, mit `redo` erneut ausführen oder mit `last` und `next` verlassen (`next` führt einen eventuell vorhandenen `continue`-Block aus, `last` nicht). Das nutzen wir aus und schreiben unser »Perl-`Switch`« zum Beispiel so:

    SWITCH: {
       $a eq "eins" && do {
                           $a = 1;
                           last SWITCH;
                         };
       $a eq "zwei" && do {
                           $a = 2;
                           last SWITCH;
                         };
       $a eq "drei" && do {
                           $a = 3;
                           last SWITCH;
                           };
    # und so weiter
    }

Mit Pattern Matching (Mustervergleichen) geht es noch kürzer. Ich komme darauf zurück, wenn wir uns an Tag 9 mit Pattern Matching und regulären Ausdrücken befassen.

### goto

Ja, Perl unterstützt das verschriene `goto` (englisch »gehe zu«). Es ist in keinem Fall empfehlenswert, doch wenn Sie denn unbedingt wollen, können Sie `goto` auf drei Arten einsetzen:

- `goto LABEL`, wobei das Label irgendwo in Ihrem Skript stehen kann, in diesem Fall also wirklich eine Sprungmarke ist.
- `goto AUSDRUCK`, wobei der Ausdruck zu einem Labelnamen ausgewertet werden muss.
- `goto &NAME`, was nicht wirklich ein `goto` ist - es ersetzt eine laufende Unterroutine durch die Unterroutine `&NAME`. Es wird beim automatischen Laden von Subroutinen in Pakete genutzt.

Falls Sie sich für weitere `goto`-Details interessieren, schauen Sie in die perlsyn- Manpage.

## Zusammenfassung

Mit Bedingungen und Schleifen können Sie je nach Situation entscheiden, wie sich Ihr Skript verhalten soll. Diese Kontrollstrukturen sind so wichtig, dass wir schon seit dem zweiten Tag - lange bevor wir zu dieser Lektion gekommen sind - in den Beispielen mit ihnen gearbeitet haben.

Bedingungsanweisungen zweigen zu unterschiedlichen Anweisungsblöcken ab, abhängig davon, ob eine Bedingung erfüllt ist oder nicht. Sie haben die Konstrukte `if`, `if`...`else` und `if`...`elsif` kennengelernt, den Bedingungsoperator (`?`...`:`), der in andere Ausdrücke eingebaut werden kann, und Sie haben gesehen, wie Sie logische Operatoren (`&&`, `||`, `and` und `or`) zur Steuerung des Programmflusses einsetzen können.

Als nächstes haben wir Schleifen behandelt: insbesondere die `while`-Schleifen, die einen Anweisungsblock so lange ausführen, wie die Schleifenbedingung wahr ist. Sie haben das Gegenstück, die `until`-Schleife, kennengelernt, die abbricht, wenn die Bedingung wahr ist. In diesem Zusammenhang haben wir auch die `do`-Schleifen (`do`...`while` und `do`...`until`) und ihre Besonderheiten besprochen - beispielsweise dass sie gar keine echten Schleifen sind.

Die zweite Art von Schleife ist die `for`-Schleife, die ebenfalls Anweisungsblöcke wiederholt, aber geeigneter ist, wenn die Anzahl der Durchläufe von vornherein feststeht. Sie haben die `for`-Schleife mit ihrer C-ähnlichen Zählersyntax und die `foreach`-Schleife zum Durchlaufen aller Elemente in einer Liste kennengelernt.

Dann habe ich erklärt, wie Sie mit den Schleifensteuerbefehle`n next, last` und`redo` die Ausführung eines Blocks abbrechen und Teile der Schleife überspringen sowie in verschachtelten Schleifen eine bestimmte, mit einem Label versehene Schleife ansteuern können.

Schließlich haben wir die Lektion wie die beiden letzten Lektionen mit weiteren Anmerkungen zum Thema Eingabe beendet. Diesmal haben Sie gelernt, wie Sie mit de`r` <>-Syntax aus Dateien lesen und mit der Variablen`` `$`_ viele Operationen abkürzen können.

Mit der heutigen Lektion haben Sie sich die Grundlagen zu Perl vollständig angeeignet. Morgen werden wir die Woche mit einigen längeren Beispielen abschließen. Nächste Woche geht es nicht bloß weiter, nächste Woche legen wir richtig los und widmen uns einigen der mächtigsten und aufregendsten Perl-Features, darunter Pattern Matching (Mustervergleiche) und allerlei Listenakrobatik.

Sie haben heute folgende Perl-Funktionen kennengelernt:

- `do`, eine Funktion, die sich wie eine Schleife mit eine`m while` ode`r until` am Ende benimmt
- `rand,` das eine Zufallszahl zwischen 0 und ihrem Argument generiert,
- `srand,` den vo`n rand` verwendeten Zufallsgenerator initialisiert. Ohne Argument nimm`t srand` die aktuelle Uhrzeit als Startwert.

Mehr Details finden Sie - wie bereits erwähnt - in der perlfunc-Manpage.

## Fragen und Antworten

Frage:
Ich habe den Eindruck, dass `for`-Schleifen auch als `while`-Schleifen geschrieben werden könnten und umgekehrt.

Antwort:
Ja, das könnten sie mit Sicherheit. Aber es geht darum, was Sie sich vorstellen, eine »wiederhole x-mal«- oder eine »wiederhole so lange, bis«- Schleife. Sie sollen dann nicht noch darüber nachdenken müssen, wie Sie die eine in die andere umformulieren. Es ist eine der angenehmsten Eigenschaften von Perl, dass es in der Lage ist, sich an Ihre Denkweise anzupassen. Übrigens kennen sogar die weit weniger »entgegenkommenden« Sprachen C und Java `while-` und `for`-Schleifen.

Frage:
Ich habe versucht, mit `continue` eine Schleife abzubrechen, und Perl hat mit Fehlermeldungen um sich geworfen. Was habe ich falsch gemacht?

Antwort:
Sie haben vergessen, dass `continue` in Perl nicht zum Abbrechen von Schleifen verwendet wird (oder Sie haben diesen Abschnitt übersprungen). Die Entsprechung zum `continue` von C ist in Perl `next`. Verwenden Sie `continue` nur als optionalen Anweisungsblock, der am Ende eines Blocks ausgeführt werden soll (siehe Vertiefungsabschnitt).

Frage:
Ich habe mir Ihre beiden Beispiele für das Lesen aus Dateien angesehen. Eines der Beispiele verwendet `$_`, das andere nicht. Das erste ist zwar kürzer, aber wenn man nicht weiß, welche Operationen mit `$_` arbeiten, ist es kaum zu verstehen. Ich finde, dass die Lesbarkeit eines Skripts ein paar mehr Buchstaben wert ist.

Antwort:
Das ist definitiv ein guter Grundsatz, dem auch viele Perl-Programmierer folgen. »Schleichwege« über die `$_`-Variable machen das Skript zwar kürzer, aber bestimmt auch weniger verständlich. In manchen Fällen - wie zum Beispiel für Eingaben mit `<>` -`` ist der Einsatz von `$_` jedoch eine Art weitverbreitete »Redensart«, die, sobald Sie sich daran gewöhnt haben, auch vernünftig und verständlich scheint. Wenn Sie Skripts von anderen lesen oder verändern müssen, werden Sie wahrscheinlich recht bald auf mysteriöses `$_`- Verhalten stoßen. Achten Sie also auf `$_`, setzen Sie es ein, wo es wirklich angemessen ist, oder vermeiden Sie es, wenn es Ihrer Meinung nach die Lesbarkeit zu sehr einschränkt. Die Entscheidung liegt ganz bei Ihnen.

## Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie zur Lektion des nächsten Tages übergehen.

### Quiz

1. Was ist ein Block? Was kann ein Block enthalten?
2. Was ist der Unterschied zwischen `if` und `unless`?
3. Wodurch wird eine `while`-Schleife abgebrochen?
4. Warum sind `do`-Schleifen anders als `while-` oder `until`-Schleifen?
5. Welche drei Ausdrücke enthält die Klammer der `for`-Schleife? Was machen sie?
6. Erklären Sie den Unterschied zwischen `next`, `last` und `redo`.
7. Nennen Sie drei Situationen, in denen die Variable `$_` verwendet werden kann.
8. Worin unterscheidet sich `<>` von `<STDIN>`?

### Übungen

1. Schreiben Sie ein Divisions-Skript, in dem Sie um die Eingabe von zwei Zahlen bitten und prüfen, dass keine der eingegebenen Zahlen negativ und die zweite Zahl nicht Null ist. Wenn beide Zahlen diesen Anforderungen entsprechen, teilen Sie die erste durch die zweite Zahl, und geben Sie das Ergebnis aus.
2. FEHLERSUCHE: Was stimmt nicht an diesem Skript? (Tipp: Es könnten auch mehrere Fehler sein)

           if ($wert == 4) then { print $wert; }
       elseif ($wert > 4) { print "mehr als 4"; }

3. FEHLERSUCHE: Und was ist falsch an diesem hier (es könnten auch hier mehrere Fehler sein)?

           for ($i = 0, $i < $max, $i++) {
          $werte[$i] = "";
       }

4. FEHLERSUCHE: Und was ist mit diesem hier?

           while ($i < $max) {
          $werte[$i] = 0;
       }

5. Schreiben Sie das Skript `namen.pl` so um, dass es die Namen aus einer Datei liest (wenn Sie gestern die Übungsaufgabe 3 gelöst haben, also auch mit Zweitnamen umgehen können, schreiben Sie diese Version entsprechend neu).

## Antworten

Hier die Antworten auf die Workshop-Fragen aus dem vorigen Abschnitt.

### Antworten zum Quiz

1. Ein Block ist eine von geschweiften Klammern (`{}`) umgebene Gruppe von Perl- Anweisungen (die andere Blöcke enthalten kann). Blöcke werden meistens im Zusammenhang mit Bedingungen und Schleifen gebraucht, können aber auch allein, als freistehende Blöcke, verwendet werden (bare blocks).
2. Eine `if`-Anweisung führt ihren Block aus, wenn der Bedingungsausdruck zu wahr ausgewertet wird, `unless` führt ihn aus, wenn die Bedingung falsch ergibt.
3. Die `while`-Schleife bricht ab, wenn ihr Bedingungsausdruck falsch zurückgibt.
4. `do`-Schleifen unterscheiden sich in zweierlei Hinsicht von `while-` oder `until`- Schleifen. Zum einen werden ihre Blöcke vor der ersten Überprüfung, also mindestens einmal, ausgeführt. Zum anderen können Sie keine Schleifensteuerbefehle wie `last` oder `next` in einer `do`-Schleife verwenden, weil `do` gar keine echte Schleife, sondern eigentlich eine Funktion mit einem Modifikator ist.
5. Die drei Teile in den Klammern der `for`-Schleife sind:
6. a) Ein Ausdruck zum Initialisieren der Laufvariablen/des Schleifenzählers, wie `$i = 0.`
7. b) Ein Bedingungsausdruck, der die Anzahl der Durchläufe bestimmt, wie zum Beispiel `$i < $max.`
8. c) Ein Inkrementierungsausdruck, der den Schleifenzähler verändert (damit die Schleifenbedingung irgendwann nicht mehr erfüllt ist), zum Beispiel `$i++.`
9. `next`, `last` und `redo` sind Schleifensteuerbefehele. `next` bricht den aktuellen Durchlauf ab und startet am Schleifenanfang den nächsten, inklusive der Überprüfung der Bedingung in einer `for`- oder `while`-Schleife. Auch `redo` bricht den aktuellen Durchlauf ab, startet aber am Anfang des Blocks, ohne die Schleifenbedingung zu überprüfen. `last` beendet die Schleife ebenso komplett wie abrupt - es prüft nichts, startet nichts, es steigt einfach aus.
10. Hier ein paar Situationen, in denen Perl `$_` verwendet, wenn Sie keine Variable angegeben:
11. `while (<>)` weist `$_` jede Zeile einzeln zu.
12. `chomp` entfernt den Zeilenvorschub vom String in `$_.`
13. `foreach` verwendet `$_` als temporäre Schleifenvariable.
14. `<>` wird verwendet, um die Inhalte von Dateien einzulesen, die über die Kommandozeile angegeben (und in `@`ARGV gespeichert) werden. Mit `<STDIN>` liest man Daten von der Standardeingabe (normalerweise der Tastatur) ein.

### Lösungen zu den Übungen

1. Hier eine mögliche Lösung:

           #!/usr/bin/perl -w

       $zahl1 = 0;
       $zahl2 = 0;

       while () {
           print 'Geben Sie die erste Zahl ein: ';
           chomp($zahl1 = <STDIN>);
           print 'Geben Sie die zweite Zahl ein: ';
           chomp($zahl2 = <STDIN>);

           if ($zahl1 < 0 || $zahl2 < 0) {
               print "Keine negativen Zahlen!\n";
               next;
           } elsif ( $zahl2 == 0) {
               print "Die zweite Zahl darf nicht 0 sein!\n";
               next;
           } else { last; }
       }

       print "$zahl1 geteilt durch $zahl2 ist ";
       printf("%.2f\n", $zahl1 / $zahl2 );

2. Es sind zwei Fehler: `elseif` ist kein gültiges Perl-Schlüsselwort, nehmen Sie statt dessen `elsif`. `then` ist ebenfalls kein gültiges Perl-Schlüsselwort.
3. Die Ausdrücke innerhalb des `for`-Bedingungsausdrucks müssen mit Semikola, nicht mit Kommata getrennt werden.
4. Syntaktisch ist diese Schleife korrekt, aber sie wird nie enden - `$i` wird innerhalb der Schleife nicht inkrementiert.
5. Hier eine Antwort:

           #!/usr/bin/perl -w

       %names = ();   # Hash: Namen
       @raw = ();     # temp: rohe Woerter
       $fn = '';      # Vorname

       while (<>) {
           chomp;
           @raw = split(" ", $_);
           if ($#raw == 1) {  # Normalfall: zwei Woerter
               $names{$raw[1]} = $raw[0];
           } else {  # den Vornamen zusammensetzen
               $fn = '';
               for ($i = 0; $i < $#raw; $i++) {
                   $fn .= $raw[$i] . " ";
               }
               $names{$raw[$#raw]} = $fn;
           }
       }

       foreach $lastname (sort keys %names) {
           print "$lastname, $names{$lastname}\n";
       }

© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH