Die hier aufgeführten Beispielprogramme sind ohne Einschränkungen funktionsfähig und können für weitere Ausbauten leicht erweitert werden.
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" ; } }
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.
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.
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 .
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.
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.
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"; }
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>}; } }
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.