CLUE Computerlinguistik Uni Erlangen
Vorher Zurück Weiter
Weiter: Achte Sitzung Zurück: Unterlagen Vorher: Sechste Sitzung

Siebte Sitzung - Einführung in Perl

Ein Bummel durch Perl

Die Liste ein klein wenig sicherer machen

Zur Sicherheit soll die Liste der geheimen Wörter einmal pro Woche geändert werden. Nun, das können wir nicht erzwingen, aber wir können eine Warnung ausgeben, wenn sie es nicht wurde.

Am besten läßt sich dies in dem Unterprogramm &InitialisiereWoerter unterbringen, da wir uns dort die Datei sowieso ansehen. Dazu verwenden wir den -M-Operator, welcher die Anzahl der Tage liefert, die vergangen sind, seitdem eine Datei das letzte mal verändert wurde. Also müssen wir nur nachschauen, ob dieser Wert bei der Datei wortliste größer als sieben ist.

sub InitialisiereWoerter {
  open (WORTLISTE, "wortliste");
  if (-M WORTLISTE > 7) {
    die "Tut mir leid, aber die Wortliste ist älter als eine Woche!";
  }
  while ($Name = <WORTLISTE>) {
    chomp ($Name);
    $Wort = <WORTLISTE>;
    chomp ($Wort);
    $Woerter{$Name} = $Wort;
  }
  close (WORTLISTE);
}
    
Das Ergebnis von -M WORTLISTE wird also einfach mit sieben verglichen und wenn es größer ist, ist mehr als eine Woche vergangen und das Skript bricht ab. Dies geschieht mit der die-Anweisung, welche eine Meldung ausgibt.

Jemanden warnen, wenn etwas daneben geht

Wie wäre es jedesmal eine E-Mail zu versenden, wenn jemand das Programm aufruft, und das geheime Wort falsch eingibt? Dank der Modularität brauchen wir dazu nur das Unterprogramm &GutesWort zu ändern, weil hier alle Informationen stehen die wir dazu benötigen. Wir erzeugen im else-Zweig, bevor wir den Wert falsch zurückliefern, einen weiteren Filehandle für den E-Mail-Prozeß.
sub GutesWort {
  my ($IrgendeinName,$IrgendeinVersuch) = @_;
  $IrgendeinName =~ s/\W.*//;
  $IrgendeinName =~ tr/A-Z/a-z/;
  if (($IrgendeinName eq "joerg") ||
      ($IrgendeinName eq "oliver")) {
    "wahr";
  } elsif (($Woerter{$IrgendeinName} || "kokolores") eq $IrgendeinVersuch) {
    "wahr";
  } else {
    open (MAIL, "|mail joerg@linguistik.uni-erlangen.de,
oliver@linguistik.uni-erlangen.de");
    print MAIL "Schlechte Nachrichten:
$IrgendeinName hat $IrgendeinVersuch geraten\n";
    close (MAIL);
    "falsch";
  }
}
    
Der erste neue Befehl hier ist die open()-Anweisung, bei der hier, statt des Dateinamens der zu öffnenden Datei, ein UNIX-Befehl steht. Damit Perl dies erkennt, steht vor dem Befehl das Pipe-Symbol (|), das ankündigt, daß hier statt einer Datei ein Kommando geöffnet wird, an welches wir Daten übergeben wollen. Nach dem Pipe-Symbol kommt das Kommando welches ausgeführt werden soll mit seinen Parametern, hier mail mit den Parametern joerg@linguistik.uni-erlangen.de, oliver@linguistik.uni-erlangen.de. Mit der print-Anweisung werden die Daten über den Filehandle an den Befehl weitergegeben und mit der close()-Anweisung wird der Filehandle geschlossen und der Befehl ausgeführt.

Mehrere Dateien in einem Verzeichnis

Wenn es eine ganze Reihe von Dateien mit geheimen Wörter in demselben Verzeichnis gibt, die alle auf .geheim enden, können wir in der Shell den Befehl
$ ls *.geheim
    
eingeben, um uns eine Liste dieser Dateien anzeigen zu lassen. Auch in Perl gibt es eine ähnliche Syntax, die wir in dem Unterprogramm &InitialisiereWoerter verwenden wollen.
sub InitialisiereWoerter {
  while ($Dateiname = <*.geheim>) {
    open (WORTLISTE, $Dateiname);
    if (-M WORTLISTE < 7) {
      while ($Name = <WORTLISTE>) {
        chomp ($Name);
        $Wort = <WORTLISTE>;
        chomp ($Wort);
        $Woerter{$Name} = $Wort;
      }
    }
    close (WORTLISTE);
  }
}
    
Als erstes haben wir um alles herum eine neue while-Schleife gesetzt. Diese arbeitet ähnlich der anderen, nur das hier ein Verzeichnis und keine Datei eingelesen wird. Zusätzlich kann in den spitzen Klammern ein Muster angegeben werden (.geheim), welchem die einzulesenden Dateien im Verzeichnis entsprechen müssen.

So werden die geheimen Wörtern aus allen Dateien eingelesen, die im aktuellen Verzeichnis auf .geheim enden, wobei die Dateien, die älter sind als sieben Tage, ausgelassen werden.

Wir wissen wer Ihr seid!

Übrigens müssen wir überhaupt nicht den Namen der Personen erfragen welche das Skript aufruft, denn wir können ihn auch vom System bekommen, indem wir einfach die folgenden Perl-Befehle ausführen:
@Passwort = getpwuid($<);
$Name = $Passwort[6];
$Name =~ s/,.*//;
    
Die erste Zeile liefert uns die Daten des gerade eingeloggten Benutzers. Dazu wird die getpwuid-Anweisung mit der UNIX-user-ID Nummer dieses Benutzers, die von Perl automatisch in die Variable $< eingetragen wird, aufgerufen und die Daten werden an die Liste @Passwort übergeben.

Das siebte Element dieser Liste (Index 6) ist das GCOS-Feld, welches einen String enthält, der aus einer Liste von durch Komma getrennten Werten besteht, deren erster normalerweise der komplette Name des eingeloggten Benutzers ist.

Nach den ersten beiden Befehlen haben wir aber das komplette GCOS-Feld in der Variablen $Name, während der Name ganz vorne vor dem ersten Komma steht, weshalb wir den Rest mit dem dritten Befehl entfernen.

Diese Befehle werden nun an den Anfang des Skripts geschrieben.

#!/opt/perl5/bin/perl
&InitialisiereWoerter;
@Passwort = getpwuid($<);
$Name = $Passwort[6];
$Name =~ s/,.*//;
if (($Name =~ m/^joerg\b/i) ||
    ($Name =~ m/^oliver\b/i)) {
      [... Rest gelöscht ...]
    

Auflisten der geheimen Wörter

Nun wäre es noch ganz schön, die Listen mit den geheimen Wörtern auszudrucken. Dazu erstellen wir ein neues Skript, für das wir ein wenig Code vom Unterprogramm &InitialisiereWoerter klauen.
while ($Dateiname = <*.geheim>) {
  open (WORTLISTE, $Dateiname);
  if (-M WORTLISTE < 7) {
    while ($Name = <WORTLISTE>) {
      chomp ($Name);
      $Wort = <WORTLISTE>;
      chomp ($Wort);
      $Woerter{$Name} = $Wort;
### NEUE DATEN KOMMEN HIERHER
    }
  }
  close (WORTLISTE);
}
    
An der Stelle "### NEUE DATEN KOMMEN HIERHER", wissen wir alle wesentlichen Dinge, die wir ausdrucken wollen: den Namen der Datei (in $Dateiname), den Namen einer Person (in $Name) und das geheime Wort dieser Person (in $Wort). Also schreiben wir an diese Stelle die write-Anweisung, die Daten in einem bestimmten Format nach STDOUT schickt.

Dieses Format für die Ausgabe wird folgendermaßen definiert:

format STDOUT =
@<<<<<<<<<<<<<<<<<<<< | @<<<<<<<<<< | @<<<<<<<<<<
$Dateiname, $Name, $Wort
.
    
Es beginnt mit dem Schlüsselwort format, gefolgt von dem Filehandle für die Ausgabe (hier STDOUT) sowie dem Gleichheitszeichen und endet mit einem einzelnen Punkt. Die Zeilen dazwischen bestimmen das Format selber. In der ersten Zeile steht hier eine Felddefinitionszeile, welche die Anzahl, die Länge und die Art der Felder bestimmt. Danach folgt immer eine Feldwertezeile in der eine Liste von Ausdrücken steht, die ausgewertet werden, wenn das Format verwendet wird.

Zusammen mit der write-Anweisung erhalten wir das folgende Skript.

#!/opt/perl5/bin/perl
while ($Dateiname = <*.geheim>) {
  open (WORTLISTE, $Dateiname);
  if (-M WORTLISTE < 7) {
    while ($Name = <WORTLISTE>) {
      chomp ($Name);
      $Wort = <WORTLISTE>;
      chomp ($Wort);
      $Woerter{$Name} = $Wort;
      write;
    }
  }
  close (WORTLISTE);
}

format STDOUT =
@<<<<<<<<<<<<<<<<<<<< | @<<<<<<<<<< | @<<<<<<<<<<
$Dateiname, $Name, $Wort
.
    
Da die write-Anweisung bei jedem Schleifendurchlauf ausgeführt wird, bekommen wir eine Reihe von Zeilen, welche in Spalten aufgeteilt sind und unsere Daten enthalten.

Damit das ganze noch etwas besser aussieht, bekommen die Spalten noch Überschriften mit dem Format für den Seitenanfang.

format STDOUT_TOP =
Seite @<<
$%

Dateiname             | Name        | Wort
======================|=============|============
.
    
Dieses Format wird beim ersten Aufruf verwendet und dann alle weiteren 60 Ausgabezeilen. Auch hier steht in der ersten Zeile die Anzahl, die Länge und die Art der Felder, aber auch konstanter Text (Seite). Danach folgt die Liste von Ausdrücken, die ausgewertet werden, wenn das Format verwendet wird, hier die Variable $%, welche die Anzahl der gedruckten Seiten enthält. Danach folgt eine Leerzeile, sowie die weiteren Zeilen die auf jeder Seite ganz oben stehen sollen.

Alte Listen leichter erkennbar machen

Kommen wir noch einmal zurück zum ersten Skript, in welchem wir die zu alten Dateien mit geheimen Wörtern einfach auslassen. Besser wäre es, sie dann gleich in .geheim.alt umzubenennen, damit man sie mit ls gleich am Namen erkennen kann.

Also ändern wir wieder das Unterprogramm &InitialisiereWoerter:

sub InitialisiereWoerter {
  while ($Dateiname = <*.geheim>) {
    open (WORTLISTE, $Dateiname);
    if (-M WORTLISTE < 7) {
      while ($Name = <WORTLISTE>) {
        chomp ($Name);
        $Wort = <WORTLISTE>;
        chomp ($Wort);
        $Woerter{$Name} = $Wort;
      }
    } else {
      rename ($Dateiname,"$Dateiname.alt");
    }
    close (WORTLISTE);
  }
}
    
Wir haben also einen else-Zweig an das if angehängt, in welchem eine Datei die älter als eine Woche ist, mit der rename-Anweisung umbenannt wird. Diese Anweisung braucht zwei Pararmeter: den alten und den neuen Dateinamen.

Einrichten einer Datenbank

Zum Schluß wollen wir noch eine Datenbank einrichten, in welcher der letzte gültige Rateversuch für jede Person gespeichert werden soll.

Zunächst erscheint ein assoziativer Array eine brauchbare Datenart dafür zu sein. Wenn man zum Beispiel den Befehl

$LetzterErfolgreicherVersuch{$Name} = time;
    
ausführt, wird die momentane UNIX-Zeit (ein großer Integer-Wert über 700 Millionen, der jede Sekunde um eins erhöht wird) der Wert eines Elements von %LetzterErfolgreicherVersuch mit dem Schlüssel $Name. Leider wird der assoziative Array wieder gelöscht, sobald man das Skript verläßt und ein neuer angelegt, wenn man es wieder aufruft. Dies kann mit den Anweisungen dbmopen() und dbmclose() umgangen werden, die einen assoziativen Array in eine Datei abbilden.
dbmopen(%LetzterErfolgreicherVersuch,"LetzterVersuchDB",0666);
$LetzterErfolgreicherVersuch{$Name} = time;
dbmclose(%LetzterErfolgreicherVersuch);
    
Der erste Befehl übernimmt dabei das Abbilden des assoziativer Arrays %LetzterErfolgreicherVersuch in die Dateien LetzterVersuchDB.dir und LetzterVersuchDB.pag, die mit den UNIX-Zugriffsrechten 0666 (user, group und other haben Schreib- und Leserechte für diese Dateien, siehe auch die manpage für chmod) angelegt werden, wenn sie noch nicht existieren.

Im zweiten Befehl sehen wir, daß wir den abgebildeten assoziativen Array genauso wie einen normalen assoziativen Array verwenden können. Immer wenn wir diesen verändern, verändern wir auch den in der Datei gespeicherten.

Mit dem dritten Befehl, werden Datei und assoziativer Array wieder voneinander getrennt.

Diese drei Befehle werden nun in das Skript eingebaut, am besten am Ende vor der Definition der Unterprogramme.

Allerdings haben wir noch keine Möglichkeit die Daten, die von unserem Skript erzeugt werden, zu untersuchen. Deshalb schreiben wir noch ein kleines Skript, das uns die Daten der Datenbank ausdruckt.

#!/opt/perl5/bin/perl
dbmopen(%LetzterErfolgreicherVersuch,"LetzterVersuchDB",0666);
foreach $Name (sort keys (%LetzterErfolgreicherVersuch)) {
  $Wann = $LetzterErfolgreicherVersuch{$Name};
  $Stunden = (time - $Wann) / 3600;
  write;
}
dbmclose(%LetzterErfolgreicherVersuch);

format STDOUT =
Benutzer @<<<<<<<<<< : letzter erfolgreicher Rateversuch war vor @<<< Stunden.
$Name, $Stunden
.
    
Dafür, daß das Skript so klein ist, haben wir umso mehr neue Operationen. Eine foreach-Schleife, welche über die sortierte Liste der Schlüssel des assoziativen Arrays läuft.

Zuerst erhalten wir mit der keys()-Anweisung, die einen assoziativen Array als Argument nimmt, eine Liste aller Schlüssel dieses Arrays in unbestimmter Reihenfolge. Diese Liste wird dann mit der sort-Anweisung alphabetisch sortiert. Als letztes kommt die foreach-Schleife, die eine Liste Element für Element durchläuft, wobei sie den Wert des Elements jedesmal an eine skalare Variable überweist, mit dem dann der Block abgearbeitet wird.

In dem Block wird die Variable $Wann mit der Zeit des letzten erfolgreichen Rateversuchs von $Name besetzt. Diese Zeit wird von der aktuellen Zeit (die von der Funktion time geliefert wird) abgezogen und durch 3600 geteilt, um aus der Zeit in Sekunden die Zeit in Stunden zu ermitteln, die dann an die Variable $Stunden übergeben wird. Danach wird ein Format aufgerufen und das ganze ausgegeben.


Vorher Zurück Weiter
Weiter: Achte Sitzung Zurück: Unterlagen Vorher: Sechste Sitzung
Oliver Lorenz Jörg Schreiber
zuletzt geändert am 14. Juli 1998