Weiter Zurück Inhalt

6. Musterprogramme

Die hier aufgeführten Beispielprogramme sind ohne Einschränkungen funktionsfähig und können für weitere Ausbauten leicht erweitert werden.

6.1 Ein Programm zum Probieren und Üben

Manchmal kann es praktisch sein, Perl-Kommandos einzeln einzugeben, um ihre Eigenschaften zu untersuchen; es ist jedoch lästig, dafür jedes Mal den Perl-Interpreter neu aufzurufen. Mit Hilfe des eval()-Operators lassen sich Perl-Konstrukte ausführen, die auch fehlerhaft sein dürfen. Im Gegensatz zum normalen Perl-Skript wird die Ausführung dann nicht abgebrochen, sondern in der Laufzeitumgebung eine Fehlernummer und eine Kopie der vermuteten Fehlerstelle ausgegeben, die dann bei Bedarf weiterverarbeitet werden können. Das folgende Skript liest Zeilen von der Standardeingabe und versucht, sie als Perl-Kommandos auszuführen.


 
#!/usr/bin/perl
while (<STDIN>) {
        chomp;
        $command=$_;
        eval $command;
        if ($@) {
                print STDERR "Code: $command; eval: $@\n" ;
        }
}

6.2 Wortschatzstatistik

Mit dem nächsten Programm wird gezeigt, wie man mit einfachen Mitteln eine alphabetisch sortierte und gezählte Liste aller Wörter eines gegebenen Textes erstellen kann.


#!/usr/bin/perl
#
# Sehr einfaches Programm zur Zählung von Wörtern in einem Text
#

while (<>) {                         # Zeilenweises Einlesen nach $_
        split(/\W/);                    # \W trennt auf nicht-Wort-Zeichen
        foreach $wort (@_) {            # Einzelne Wörter befinden sich in @_
                $wortliste{$wort}++;    # Einklinken in Hash, eventuell
        }                               # Inkrementieren des Zählers
}

# Nachdem alle Wörter gelesen worden sind,
# wird das gefüllte Hash ausgegeben.
#
foreach $wort (sort keys %wortliste) {
        print "$wort: $wortliste{$wort}\n";
}

Obwohl in dem kleinen Beispielprogramm kanonische Verfahren zur sortierten Auflistung von Schlüsseln in Hashes verwendet werden (sort keys %wortliste), ist das Programm natürlich nicht besonders leistungsfähig. Verbesserungswürdig ist zunächst der Mechanismus des Aufbrechens in einzelne Wörter. Der Reguläre Ausdruck \W ist eine verkürzte Schreibweise für [^A-Za-z] und trennt die eingelesene Zeile in ein Array, das voreingestellt in @_ vorliegt. Bedauerlicherweise erfaßt der Ausdruck \W keine deutschen Umlaute, diese müssen daher anders angegeben werden.

Auch die Ausgabe der Daten läßt in der Form noch zu wünschen übrig. Es wird als Übung für den Leser angegeben, die Ausgabe mit Hilfe von printf oder eines zu definierenden Formats in wohlgeordnete Spalten zu bringen und eventuell das Ergebnis nach Häufigkeit zu sortieren.

6.3 Buchstabenstatistik

Das Programm zur Wortschatzermittlung läßt sich mit wenigen Mitteln abändern, so daß sich nun Buchstaben zählen lassen. Buchstabenstatistiken eignen sich beispielsweise zur automatisierten Erkennung der Sprache eines Textes; darüberhinaus können mit ausgezählten Buchstabenmengen Parser gebaut und getestet werden, da jeder gefundene Buchstabe eines Eingabetextes explizit ausgegeben wird und nicht übersehen werden kann.


#!/usr/bin/perl -w
# at least this is where my perl should reside
#
#
# Read all data
#
while (<>) {
        s/sh/S/g;
        @letters=split(/[ \t\n]*/);
        foreach $letters (@letters) {
                if (ord $letters>0) 
                        {
                        $hash{$letters}++};
                        }
        }
# Spit out all data
#
foreach $key (sort {$hash{$b} <=> $hash{$a} } keys %hash)
        {
        printf "%8d %s -> %3d\n", $hash{$key}, $key, ord $key;
        }

Um die gefundenen Daten nach Häufigkeit zu sortieren, muß der zu einem Schlüssel (keys %hash) gefundene Wert numerisch sortiert werden. Im optionalen Block der Funktion sort, der hier in geschweiften Klammern angegeben ist, wird ein Vergleich der gefundenen Werte (die temporär in die vordefinierten Variablen $a und $b eingelesen werden) ausgeführt. Wird die Vergleichsoperation mit <=> angegeben, so werden die Werte numerisch sortiert; würde stattdessen cmp stehen, so fände ein Zeichenkettenvergleich statt.

6.4 Rückläufig sortierte rechtsbündige Ausgabe einer Wortliste

Mit den folgenden Lösungsvarianten der Aufgabe, eine Wortliste rückläufig sortiert rechtsbündig auszugeben, läßt sich eindrucksvoll die Mächtigkeit von Perl demonstrieren. Rückläufig sortierte Wortlisten werden vielfach eingesetzt, um Wortbildung und grammatische Endungen zu untersuchen, aber auch, um verstümmelte Texte zu entziffern.

Die erste angegebene Lösung geht noch einen relativ wenig idiomatischen Weg zum Ziel.


#!/usr/bin/perl

@worte=();               # Initialisieren

@worte=<STDIN>;               # Worte einlesen       

@etrow= sort {reverse($a) cmp reverse($b)} @worte;  # sortieren

foreach $wort (@etrow) { # Rückläufig sortiertes Array ausgeben
        write;
}
                         # Dabei wird folgendes Format benutzt
format =
@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
$wort
.

Zuerst wird ein leeres Array namens @worte initialisiert, das dann die Eingabe von STDIN als Listenkontext liest. Anschließend wird das Array sortiert, wobei sort mitgeteilt wurde, daß ein Stringvergleich (cmp auf die gewendeten Zeichenketten (reverse($a), reverse($a)) durchzuführen ist. Das solchermaßen sortierte Array wird in @etrow abgelegt, und dann Element für Element mit write dem unbenannten Format auf STDOUT übergeben, das für die rechtsbündige Ausgabe zuständig ist.

in idiomatischem Perl-Stil spart man sich den ganzen Vorlauf und das benannte Array @worte, ebenso das Array @etrow zur Zwischenablage der Ergebnisse. Stattdessen läßt sich die Aufgabe auch mit dem folgenden, wesentlich kompakteren Code lösen:


#!/usr/bin/perl

foreach $wort (sort {reverse($a) cmp reverse($b)} <STDIN>) {
        write;
}

format =
@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
$wort
.

6.5 Akronymdatenbank, erster Anlauf

Das folgende (mehrteilige) Programmbeispiel beschreibt, wie man aus einem bestehenden LaTeX-Text eine über Schlüssel aufrufbare Datenbank machen kann. Das Textbeispiel zeigt ein Bruchstück eines Glossars mongolischer Abkürzungen, denen jeweils mehrere Felder zugeordnet sind. Das erste Feld nach der Abkürzung enthält eine laufende Nummer, falls es mehrere Bedeutungen einer Abkürzung gibt, das nächste Feld enthält die mongolische Auflösung der Abkürzung, anschließend folgen englische und deutsche Übersetzung.


\Item{A}{(1)}{antenn}{Aerial}{Antenne}

\Item{A}{(2)}{Armi}{Army}{Armee}

\Item{AA}{(1)}{Aj Axuï}{Economy}{Wirtschaft}

\Item{AAATÄSNMX}{}{Azi Afrikiïn Ard Tümniï Äw Sanaany Nägdliïn Mongolyn Xoroo}
        {Mongolian Committee for Afro-Asian Peoples' Solidarity}
        {Mongolisches Komitee für die Solidarität Afro-Asiatischer Völker}

[... viel weggelassen...]

\Item{YaÜÄEZ}{}{Yapony Üïldwärqniï Äwläliïn Erönxiï Zöwlöl}
        {Japanese Central Council of Trade Unions}
        {Japanischer Zentralrat der Gewerkschaften}

\Item{YaX}{}{Yawgan Xoroo}{Infantery Regiment}{Infanterieregiment}

\Item{YaC}{}{Yawgan Cäräg}{}{Infanterist}

Es ist nun ein Perl-Programm zu schreiben, das unter Benutzung der Datenbankdienste des Betriebssystems eine Datenbank erstellt, in der mit der Abkürzung als Schlüssel der komplette Text aus mongolischer Glosse sowie den Übersetzungen aufgerufen werden kann.

Der Einfachheit halber spalten wir das Programm in zwei Teile, die als separate Skripte gespeichert werden. Das erste Skript erstellt die Datenbank, während das zweite Skript eine einfache Abfrage an der Konsole ermöglicht. Beide Skripte greifen auf die gleiche Datenbank zu.

Skript zur Erzeugung der Datenbank

Das Skript akuimp.pl liest den Datentext von der Konsole ein, wozu der Absatzmodus aktiviert wird (perl -00). Auf diese Weise wird als Separator zwischen zwei Einträgen der Absatz (also mindestens zwei Zeilenwechsel) angenommen, und es wird unerheblich, ob die einzelnen in geschweiften Klammern stehenden Felder auf der selben Zeile stehen oder nicht. Ein einzelnes, in {} geklammertes Feld darf sich auch über mehrere Zeilen erstrecken.

Jede eingelesene Zeile wird in drei Teile geteilt: die Abkürzung, die (möglicherweise leere) laufende Nummer und den Block der Glossen und Übersetzungen. Getrennt wird mit der Folge schließende geschweifte Klammer - möglicher Zeilenumbruch, mögliche Kommentare - öffnende geschweifte Klammer. Damit Glosse und Übersetzungen tatsächlich in einem Feld verbleiben, wird split() (siehe dazu den Eintrag in der perlfunc-manpage) mit dem optionalen Argument der maximalen Feldzahl versehen:


($Abbrev,
 $Number,
 $Gloss)=split(/}[^}{]*{/,$_,3);

Da eine Abkürzung unter Umständen mehrere Bedeutungen haben kann, müssen gleiche Abkürzungen durch eine laufende Nummer unterschieden werden, damit sich verschiedene Schlüssel erzeugen lassen. Zu diesem Zweck wird einfach das Nummernfeld an das Abkürzungsfeld angehängt.

Mit dem so erzeugten (und vorher noch kosmetisch bereinigtem) Schlüssel wird nun ein namentlich referenziertes Array befragt und bei Nichtvorhandensen des Schlüssels aufgestockt:


if ($Acronyms{$Key} eq "") {    # Glosse einklinken
        $Acronyms{$Key}=$Gloss;
                

Fertig ist das ganze Import-Skript. Hier noch einmal der vollständige Text:

Die Musterdateien auf Disk stimmen nicht unbedingt absolut mit den hier angegebenen Beispieltexten überein. Bei den abgedruckten Versionen sind noch Kommentare ergänzt worden, die in den Dateiversionen nicht enthalten sind.


#!/usr/bin/perl -00                     
dbmopen(%Acronyms,"acronym",0666);      # Für Lesen und Schreiben öffnen!
while (<STDIN>) {
         s/{\\([Ss])h}/\|\\$1h\|/g;
        ($Abbrev,                       # Felder aufteilen
         $Number,
         $Gloss)=split(/}[^}{]*{/,$_,3);

         foreach $wort ($Abbrev,$Gloss) {       # Kosmetik, unerheblich
                $wort=~s/\|\\([Ss])h\|/\{\\$1h\}/g;
         }

        $Abbrev=~s/^\\Item{//;          # Abkürzungstext von
        $Abbrev=~s/[ -].*$//;           # Ballast wie Interpunktion
        $Abbrev=~s/\\?[,.\/]//g;        # und wechselnder Großschreibung
        $Abbrev=~s/./\U$&/g;        # befreien
        $Abbrev=~s/{\\SH}/SH/g;
        $Abbrev=~s/\\SH/SH/g;

        $Key=$Abbrev.$Number;           # Schlüssel bilden

        if ($Acronyms{$Key} eq "") {    # Glosse einklinken
                $Acronyms{$Key}=$Gloss;

dbmclose(%Acronyms);                    # Schotten dicht, gute Nacht.

Hinweis: Im obigen Skript werden die veralteten Perl-Aufrufe dbmopen() und dbmclose() verwendet, die ein Hash direkt an die DBM-Funktionen des Betriebssystems binden. Es wird empfohlen, sich für zukunftsfeste Applikationen an die tie()- und untie()-Funktionen zu halten, die die Anbindung an fest gespeicherte Dateien universeller gestalten, da der Benutzer in der Wahl der Datenbank nicht mehr eingeschränkt ist. Die Schnittstelle ist jetzt objektorientiert und in das Modul DB_File ausgelagert, welches Bestandteil der Standard-Installation von Perl ist (wenigstens auf UNIX-ähnlichen Systemen). Die DB_File-manpage enthält eine Reihe von Anwendungsbeispielen. Weitere ausführliche Information ist auch in der perltie-manpage enthalten.

Die Datenbankabfrage

Das Skript akuproc.pl ist nur geringfügig aufwendiger als das Skript zur Datenbankerzeugung. Es wird eigentlich nur durch Routinen aufgebläht, die das Programm benutzerfreundlicher machen. Von der Konsole wird zunächst eine Abkürzung eingelesen, wobei dem Benutzer freigestellt wird, Umlaute in "[AOU]-Notation einzugeben, falls die Tastatur keine Umlaute anbietet.

Im nächsten Schritt wird wieder ein namentlich referenziertes Array mit dem Schlüssel befragt. Findet sich kein Eintrag, so wird der Schlüssel automatisch um eine angehängte laufende Nummer ergänzt, die so lange inkrementiert wird, bis keine Einträge mehr gefunden werden.


$nummer=1;                      # Nummer bereitstellen
if ($Acronyms{$Abbreviation} eq "") {
        print "\nSorry, $Abbreviation not found...\n";
        print "Trying numbered abbreviation...\n";
        while ($Acronyms{$Abbreviation."($nummer)"}) {
                &Ausgabe($Abbreviation."($nummer)");
                $nummer++;
        }
}

Jeder gefundene Eintrag wird einer Ausgaberoutine übergeben, die hier recht primitiv gehalten ist; einerseits wird der Array-Aufruf wiederholt, andererseits könnte eine schönere (und einfacher zu schreibende) Ausgabe mit Hilfe des format-Befehls erreicht werden. Dies wird sicher dann nötig, wenn mehr als drei Felder in unterschiedlicher Anordnung auszugeben sind.


#!/usr/bin/perl                         # Pfad zu Perl

dbmopen(%Acronyms,"acronym",0444);      # Lesendes Öffnen der Datenbank

print "  Acronym: ";                    # Rudimentäres Prompt

while ($Abbreviation=<STDIN>) {
        chomp $Abbreviation;            # Leere Zeile bricht ab (s.  u.)
        ($Abbreviation gt "") || die "Program aborted.";
        $Abbreviation=~s/"A/Ä/g;   # Umlaute bereitstellen
        $Abbreviation=~s/"O/Ö/g;
        $Abbreviation=~s/"U/Ü/g;
        $Abbreviation=~s/"a/ä/g;
        $Abbreviation=~s/"o/ö/g;
        $Abbreviation=~s/"u/ü/g;
        $Abbreviation=~s/([Ss])h/\\|$1h|/g; # 

        $nummer=1;                      # Nummer bereitstellen

        if ($Acronyms{$Abbreviation} eq "") {   # Nichts enthalten,
                                                # probiere es mit
                                                # angehängter Nummer
                                                # nochmal... also
                                                # AA(1) und AA(2)
                                                # kommen so zum
                                                # Vorschein...
                                                # 
                print "\nSorry, $Abbreviation not found...\n";
                print "Trying numbered abbreviation...\n";
                while ($Acronyms{$Abbreviation."($nummer)"}) {
                        &Ausgabe($Abbreviation."($nummer)");
                        $nummer++;
                }
        }
        else {  
                &Ausgabe($Abbreviation);
        }
        print "  Acronym: ";                    # Wieder das Prompt
}
################################################################
sub Ausgabe {
        $Feld = shift @_;
        print "\n-----------------------------------------\n";
        $Resultat=$Acronyms{$Feld};
        $Resultat=~s/{\\([Ss])h}/$1h/g;
        ($Mong,$Eng,$Deu)=split(/}[^{}]*{/,$Resultat);

        $Deu =~ s/}[^}]*$//;

        print "Mongolian: $Mong\n";
        print "  English: $Eng\n";
        print "   German: $Deu\n\n\n\n";
}

6.6 Akronymdatenbank, zweiter Anlauf (CGI)

Interessant wird eine Datenbank, wenn der Inhalt ohne großen Aufwand vielen Benutzern zur Verfügung gestellt werden kann. Perl bringt von Haus aus kein GUI (Graphical User Interface) mit, aber es gibt ja mit den WWW-Browsern einen betriebssystemübergreifenden Quasi-Standard für die Darstellung der verschiedensten Daten. Nahezu alle Browser sind in der Lage, Formulare darzustellen, die dem Benutzer auch Texteingaben ermöglichen. Für Perl gibt es das Modul CGI, das Objekte zur Gestaltung von WWW-Seiten anbietet. Die CGI-manpage ist recht umfangreich; außerdem gibt es noch eine Reihe von Beispielskripten, die bei der Installation automatisch in ein passendes html-Verzeichnis gestellt werden. Die Installation eines WWW-Servers wird hier nicht weiter erklärt, da sie an anderer Stelle ausführlich beschrieben ist.

Es gibt eine reichliche Literaturauswahl über die CGI-Programmierung; für Linux-Systeme bietet sich auch das WWW-mSQL-HOWTO an, das leider in einigen Punkten nicht den aktuellsten Versionsstand widerspiegelt.

Im Gegensatz zur Kommandozeilenversion der Abkürzungsdatenbank verfügt die WWW-Version über ein Fenster, in das der Suchbegriff eingegeben wird; die Ausgaben erscheinen auf der gleichen Seite. Der folgende Code-Ausschnitt ist unvollständig; es werden nur die Erweiterungen gezeigt, die für die Verwendung als CGI-Skript notwendig sind.


#!/usr/bin/perl

use CGI;

dbmopen(%Acronyms,"acronym",0444);
################################################################
#
# Wir beginnen mit der HTML-Ausgabe
#
$query = new CGI;

print $query->header;
print $query->start_html('3. Access the database');
# [...Auslassung, nicht funktionsbeeinträchtigend...]
print $query->startform.
                q{<TABLE>
                <TR><TD ALIGN=RIGHT>Abbreviation:</TD>
                    <TD ALIGN=LEFT>}.$query->textfield(-name=>'Abbreviation',
                                -default=>'',
                                -columns=>20).q{</TD><TD>};
print   $query->submit(-name=>'Knopf',
                        -value=>'Query').
        $query->defaults('Clear Form').q{</TD></TR></TABLE>}.
        $query->endform;
#       $query->dump.           # Wenn's Probleme gibt...

&Abfragen if ($query->param('Knopf') eq 'Query');

print $query->end_html;

sub Abfragen {
        $Abbreviation=$query->param('Abbreviation');
        $MailSubject=$Abbreviation;
        
# [... Auslassungen...]

        if ($Acronyms{$Abbreviation} eq "") {   # Nichts enthalten;
        }                                       # Fehlermeldung
        else {                                  # nicht vergessen!
                &Ausgabe($Abbreviation);
        }
}
################################################################
sub Ausgabe {
# [... Auslassungen...]
        ($Abbrev,$Mong,$Eng,$Deu)=split(/}[^{}]*{/,$Resultat);

        $Deu =~ s/}[^}]*$//;    # Letzten Wert von Begrenzern befreien

# Eigentliche Ausgabe als HTML-Tabelle
#
        print q{<HR><TABLE>};
        print "<TR><TD ALIGN=RIGHT>Acronym:</TD><TD ALIGN=LEFT>$Abbrev</TD></TR>";
        print "<TR><TD ALIGN=RIGHT>Mongolian:</TD><TD ALIGN=LEFT>$Mong</TD></TR>";
        print "<TR><TD ALIGN=RIGHT>English:</TD><TD ALIGN=LEFT>$Eng</TD></TR>";
        print "<TR><TD ALIGN=RIGHT>German:</TD><TD ALIGN=LEFT>$Deu</TD></TR>";
        print q{</TABLE>};

# Einbettung einer Benachrichtigung des Datenbankverantwortlichen
# bei unvollständiger Erklärung und Übersetzung
#
        if ($Mong eq "" || $Eng eq "" || $Deu eq "") {
        print qq{Please mail corrections and addenda to the 
        <A HREF="mailto:infomong\@zedat.fu-berlin.de?subject=acrobase
         entry: [$Feld]?">editor</A>
                at <TT>infomong\@zedat.fu-berlin.de</TT>};
        }
}

6.7 Aufgabe: Entwicklung einer interaktiven Perl-Shell

Perl ist als mächtige Universalsprache bekannt, die theoretisch auch als Shell verwendet werden kann. Bereits das Übungsprogramm (siehe testbed ) hat ja gezeigt, wie es mit wenigen Zeilen möglich ist, Perl-Texte zu schreiben, die sich selbst vor provozierten Abstürzen retten können. Das Übungsprogramm hat jedoch in der angegebenen Form einen entscheidenden Nachteil: Jeder am Prompt übergebene Konstrukt muß syntaktisch vollständig sein. Ein Ausdruck, der sich über mehrere Zeilen erstreckt, ist nicht zulässig.

Aufgabe: Testen Sie das Übungsprogramm, indem Sie nur einen Blockanfang (geöffnete geschweifte Klammer {) eingeben. Beobachten Sie die Reaktion. Erweitern Sie das Programm so, daß es auch in der Lage ist, mehrzeilige Konstrukte zu verarbeiten. Versetzen Sie sich dazu in die Lage eines Shell-Programmierers, der eine Schleife mit for i in (*) beginnt, auf der nächsten Zeile ein do eingibt, dann auf der nächsten Zeile die nötigen Kommandos eingibt und das Ganze mit done abschließt. Ergänzen Sie das Programm nach erfolgreicher Fertigstellung um eine Editier- und Aufrufmöglichkeit für bereits eingegebene Zeilen.

Die interessantesten Lösungen werden an dieser Stelle vorgestellt.


Weiter Zurück Inhalt