Perl für Philologen

Warum sollen Philologen programmieren? Das können doch andere besser. Leider gibt es bei der Erstellung und Auswertung elektronischer Texte immer wieder Situationen, in denen Werkzeuge nötig wären, die über die einfachen Funktionen hinausgehen, die in einer Textverarbeitung angeboten werden. Aber schon aus praktischen Gründen kann man dann zumeist keinen professionellen Programmierer bemühen und versucht dann eine Aufgabe mit höchst unzureichenden Mitteln zu lösen. Da Perl im Augenblick die einfachste und für die Bearbeitung von Texten mächtigste Scriptsprache ist, soll von den sehr umfassenden Möglichkeiten Perls eben soviel vermittelt werden, wie zur Lösung einfacherer Aufgaben notwendig ist. Die wenigen Stunden, die man braucht, um sich dieses Perl für Philologen anzueigenen, sind bald wieder gewonnen, wenn man dieses nützliche Werkzeug in Projekten einsetzen kann.

Erste Schritte

Perl ist eine einfache, aber mächtige Scriptsprache, die auf fast allen Plattformen läuft. Im Gegensatz zu einem kompilierten Programm, das in einer binären Maschinensprache vorliegt und nur auf dem jeweiligen Prozessor ablauffähig ist, für den es kompiliert wurde, liegt der Programmcode von Skriptsprachen wie Perl als ASCII-Datei vor und ist überall dort ablauffähig, wo der zugehörige Interpreter läuft.
Um Perl zu verwenden brauchen Sie also einen Perl-Interpreter und einen ASCII-Editor, um die Scripte zu erstellen. Beides kann man kostenlos im Internet finden (Adressen im Anhang).

Um ein Skript zu erstellen, schreibt man den Programmtext in eine ASCII-Datei. Name und Endung sind prinzipiell egal, aber es gibt die nützliche Konvention Perl-Skripte mit der Endung .pl zu versehen.
Um ein Perl-Skript zu starten, ruft man den Interpreter auf und übergibt dem Interpreter das gewünschte Skript als Argument beim Aufruf. Der Interpreter wird mit dem Befehl perl gestartet. Soll etwa das Skript "hallo.pl" gestartet werden, würde die Befehlzeile so aussehen:
perl hallo.pl

Nun ein erstes Skript (die Striche vor und nach dem Skript dienen nur der Kennzeichnung von Dateianfang und -ende):
________________________________________________
#Das erste Programm
print "Hallo Welt";
________________________________________________
Die erste Zeile des Programms ist lediglich ein Kommentar. Alles, was in der Zeile eines Perl-Skripts nach dem Zeichen # steht, wird als Kommentar behandelt.
"print" ist die Perl-eigene Anweisung zur Ausgabe. "Hallo Welt" wird als 'Argument' der Funktion 'print' bezeichnet.
Jede Befehlszeile von Perl muß mit einem Semikolon abgeschlossen werden.
Das Programm schreibt auf den Bildschirm die Worte "Hallo Welt".

Skalare Variablen

Als Variablen bezeichnet man Platzhalter in einem Programm, deren Wert im Programm wechseln kann. In Perl sind Variablen einfach daran zu erkennen, daß sie jeweils mit einem bestimmten Zeichen beginnen. Es gibt mehrere Gruppen von Variablen, hier soll erst einmal nur die "skalare Variable" interessieren. Ihr Bezeichner beginnt mit einem $-Zeichen, z.B. $name. Nach dem $ können Buchstaben und Zahlen stehen. Umlaute und andere Sonderzeichen sind zu vermeiden (Gültige Namen: $a, $hallo, $TEXT1)
Einer Variablen kann man mit dem =-Zeichen einen Wert zuweisen:
$name = "Goethe";
$summe = 22;
Diese Zuweisung kann öfter vorgenommen werden:
$zahl = 1;
$zahl = 5;
Falls Sie schon einmal mit anderen Programmiersprachen gearbeitet haben, werden Sie es vielleicht gewohnt sein, daß man 1. Variablen vor ihrer Verwendung definieren und 2. ihren Typ festlegen muß. Beides ist in Perl nicht notwendig. Der Interpreter erschließt aus dem Kontext, ob der Wert einer Variablen eine Zeichenkette oder eine Zahl ist.
Variablen können natürlich auch mit Funktionen verwendet werden:
$name = "Goethe";
print $name;
Das Ergebnis dieses Programms ist die Ausgabe des Wortes "Goethe" auf dem Bildschirm.

Operatoren

Mit Operatoren können Konstanten oder Variablen verbunden werden. Z.B. kann man zwei Zahlen mit Operatoren addieren (+), subtrahieren (-), dividieren (/) und multiplizieren (*). Ebenso können Sie auf diese Weise zwei Zeichenketten miteinander verbinden (.) oder sogar multiplizieren:
$summe = 4+7;
$zeile = "hallo " . "welt"; # bei Ausgabe: hallo welt
$zeile = "hallo" x 3; # bei Ausgabe: hallo hallo hallo
Ein wichtige und häufige Verwendung von Operatoren ist das Verändern des Wertes einer Variablen, wobei die Variable anschließend den neuen Wert hat.
Bsp.:
$summe = 4;
$summe = $summe + 6; #weist der Variablen $summe den alten Wert um sechs erhöht zu
Besonders häufig wird der Wert um eins erhöht oder vermindert:
$summe = $summe + 1;
Kurzform:
$summe++;

$summe = $summe - 1;
Kurzform:
$summe--;


Benutzereingabe

Es gibt zwei wichtige Formen der Benutzereingabe in Perl. Zum einen kann man das Programm anhalten lassen und den Benutzer nach der Eingabe fragen. Zum anderen kann der Benutzer beim Aufruf des Programms eine wichtige Information für den Programmablauf als Argument angeben, z.B. den Namen der Datei, die bearbeitet werden soll.

Interaktive Benutzereingabe

Die interaktive Benutzereingabe wird durch die folgenden beiden Befehle erreicht:

#Schreibt die Eingabeaufforderung auf den Bildschirm
print "Bitte geben Sie Ihren Namen ein: ";

#Weist die Benutzereingabe einer Variablen als Wert zu
#STDIN steht für Standard in, d.i. die Tastatur
$name = <STDIN>;

Sobald der Benutzer seine Eingabe mit RETURN abschließt, wird seine Eingabe in der Variablen (hier also in $name) gespeichert.

Ausf. Bsp.:
________________________________________________
#Fragt nach Vorname und Nachname und gibt diese dann aus

print "Bitte geben Sie Ihren Vornamen ein: ";
$vorname = <STDIN>;
chop $vorname;

print "Bitte geben Sie Ihren Nachnamen ein: ";
$nachname = <STDIN>;

print "Ihr Name lautet: $vorname $nachname";
________________________________________________

An diesem Programm sind zwei Dinge erläuterungsbedürftig. Der Befehl "chop" entfernt das letzte Zeichen der Variablen $vorname. Warum das? Weil das letzte Zeichen der Benutzereingabe immer das RETURN-Zeichen ist, mit dem die Eingabe abgeschlossen wird. Damit dieses Zeichen aber nicht ausgegeben wird - Vor- und Nachname würden dann in zwei Zeilen stehen - muß es entfernt werden.
Ein weiterer Punkt ist der Umstand, daß die Angabe, was mit 'print' auf dem Bildschirm ausgegeben werden soll, in Anführungszeichen steht. Allerdings erkennt Perl, daß es sich bei $vorname und $nachname um Bezeichner für Variablen handelt und setzt den Wert der Variablen ein. Will man das nicht, soll also die Ausgabe auf dem Bildschirm tatsächlich die Zeichenkette "Ihr Name lautet: $vorname $nachname" sein, dann muß man entweder die Zeichenkette in einfache Anführungszeichen setzen oder einen Backslash vor die Variablenbezeichner stellen:
print 'Ihr Name lautet: $vorname $nachname';
print "Ihr Name lautet: \$vorname \$nachname";

Benutzereingabe beim Programmaufruf

Eine Benutzereingabe, die gleich beim Aufruf des Programm angegeben wird, wird in einer festgelegten internen Variablen gespeichert und kann somit einfach verwendet werden. Die interne Variable ist jedoch nicht eine skalare Variable, sondern ein sogenannter Array (Liste). Wir interessieren uns hier aber nur für den ersten Eintrag in dieser Liste - und der ist wiederum eine skalare Variable, die aber etwas anders angesprochen wird: $ARGV[0]. Der zweite Eintrag in der Liste wird mit $ARGV[1] angesprochen usw.

Bsp.:
________________________________________________
$name = $ARGV[0];
print "Hallo " . $name;
________________________________________________
Dieses Programm könnte name.pl heißen. Es würde dann z.B. so aufgerufen werden:
perl name.pl Goethe
Die Ausgabe wäre dann
Hallo Goethe

Schleifen, Bedingungen, Iteratoren

Die drei Anweisungen für Schleifen, Bedingungen und Iteratoren dienen zur Wiederholung oder zur bedingten Ausführung eines Programmteils.

Bedingungen

Alle drei verwenden Bedingungen. Eine Bedingung hat einen Wahrheitswert (wahr, falsch). Zumeist werden diese Bedingungen mit Vergleichsoperatoren geschrieben:
Für Zahlen:
==
gleich
!=
ungleich
>
größer als
<
kleiner als
>=
größer gleich als
<=
kleiner gleich als

Für Zeichenketten:
eq
gleich
ne
ungleich
>
größer als*
<
kleiner als* [*]

Bsp:
3 == 3 (wahr)
3 == 4 (falsch)
3 != 4 (wahr)

$i = 1;
$i < 3 (wahr)

$z = "Hallo";
$z eq "hallo" (falsch)

Diese Bedingungen können nun in den drei Anweisungen verwendet werden.

Bedingte Verzweigung mit 'if'

Bedingungsabfrage:

if (bedingung ist wahr) {
     Programmblock I
} elsif (2. bedingung ist wahr) {
     Programmblock II
.....
} else {
     Programmblock III
}

Der Programmblock I wird nur ausgeführt, wenn die 1. Bedingung wahr ist. Der Programmblock II wird nur ausgeführt, wenn die 2. Bedingung wahr ist usw. (beliebig viele Ergänzungen sind möglich). Wenn alle Bedingungen falsch sind, dann wird der Programmblock III ausgeführt.
Wie die Beispiele zeigen, sind kürzere Fassungen möglich.
_______________________________________________
$i = 3;
if ($i<5) {
     print "hallo";
}
_______________________________________________

$passwort = "goethe";
if ($passwort eq "goethe") {
     print "Sie sind eingeloggt.";
} else {
     print "Falsches Passwort.";
}
_______________________________________________
$passwort = "goethe";
if ($passwort eq "goethe") {
     print "Sie sind eingeloggt.";
} elsif ($passwort eq "") {
     print "Sie müssen ein Passwort eingeben.";
} else {
     print "Falsches Passwort.";
}
_______________________________________________

Schleife mit 'while'

Schleife: while. Solange die Bedingung wahr ist, wird der Programmblock ausgeführt:

while (bedingung ist wahr) {
     Programmblock
}

Bsp.:
$i = 1; $z = 5;
while ($i != $z) {
     $i = $i+1; # kann auch einfach $z++; geschrieben werden
     print "hallo ";
}


ACHTUNG: Ein beliebter Programmierfehler ist das Erzeugen einer endlosen Schleife. Wenn die Bedingung niemals falsch wird, dann läuft die Schleife endlos weiter.

Schleife mit 'for'

Kürzer läßt sich eine Schleife, die Variablen schrittweise verkleinert oder vergrößert mit 'for' formulieren.

for (startwert; bedingung; inkrement) {
     Programmblock
}

Bsp.:
for ($i = 0; $i < 5; $i++) {
     print "hallo ";
}

Öffnen einer Datei

Um eine Datei zu öffnen, muß der Name der Datei einer sogenannten file handle zugewiesen werden. Die Anweisung dafür lautet “open”. Bsp.:
open (INPUT, "text.txt");
Anschließend kann man mit der file handle , deren Name frei wählbar ist, auf den Inhalt der Datei zugreifen. Der nachstehende Befehlt liest eine Zeile aus der Datei:
$zeile = <INPUT>;
Der oben angeführte Befehl öffnet die Datei zum Lesen. Mit dem Zusatz eines Zeichens (>) wird die Datei zum Schreiben geöffnet:
open (INPUT, ">text.txt"); #besser verständlich:
open (OUTPUT, ">text.txt");
ACHTUNG! Mit diesem Befehl wird die Datei neu angelegt, d.h. eine evtl. bestehende Datei dieses Namens wird gelöscht. Will man an eine bestehende Datei etwas anfügen, lautet der Befehl dafür:
open (OUTPUT, ">>text.txt");

Da es beim Öffnen der Datei aus vielen Gründen zu Problemen kommen kann, wird der Befehl zum Öffnen der Datei normalerweise mit einer Kondition verbunden:
open (INPUT, "text.txt") || die "Probleme beim Öffnen von text.txt");
Die beiden Striche in der Mitte sind ODER-Zeichen. Wenn die Rückgabe des ersten Befehls negativ ist, dann wird der zweite Befehl ausgeführt. Dieser Befehl beendet das Programm und zeigt dabei den Text auf dem Bildschirm an.

Ein- und Ausgabe einer Datei

Zuerst muß die Datei geöffnet werden.
Zur Eingabe kann man normalerweise so vorgehen:
while ($input = <INPUT>) {
tu was;
}
Das Programm liest solange eine Zeile aus der Datei, wie dort Zeilen sind. Im while-Block kann man mit der Zeile, die in der Variablen $input gespeichert ist, etwas tun. Der Ausdruck ($input = <INPUT>) gibt solange den Wert 'wahr' zurück, wie der Variablen $input ein neuer Wert aus der Datei zugewiesen werden kann.


Ausgabe:
print OUTPUT "und tschüß";
Schreibt den Text in die angegebene Datei.

Ausf. Beispiel
________________________________________________
#Dieses Programm liest den Text aus text.txt und
#fügt vor jeder Zeile das Wort 'Text' ein. Ausgabe
#in textneu.txt

open (INPUT, "text.txt");
open (OUTPUT, ">textneu.txt");

while ($input = <INPUT>) {
print OUTPUT "Text: $input";
}
________________________________________________


Eine Standardprogrammform sieht so aus:
________________________________________________
open (INPUT, "$ARGV[0]") || die "Kann $ARGV[0] nicht öffnen" ;
open (OUTPUT, ">$ARGV[1]") || die "Kann $ARGV[1] nicht anlegen";

while ($input = <INPUT>) {
tu was;
}
________________________________________________
Wenn dieses Programm 'ersetze.pl' heißt, dann wird es so aufgerufen:
perl ersetze.pl text.txt textneu.txt

Suchen und Ersetzen


Sucht nach 'vorher':
$zeile =~ m/vorher/;

Meist in dieser Weise verwendet:
if ($zeile =~ m/vorher/) {
     tu was;
}
Wenn die Zeile 'vorher' enthält, dann tu was.

Ersetzt in $zeile alle Vorkommen von 'vorher' gegen 'nachher'.
$zeile =~ s/vorher/nachher/g;

Man kann auch Sonderzeichen verwenden:
\n Zeilenwechselzeichen (Returnzeichen)
\t Tabulator
\d Zahlen
^ Zeilenanfang
$ Zeilenende

Diese Sonderzeichen können teilweise (\n \t) auch bei der Ausgabe verwendet werden.

Gesamtbeispiel:
_______________________________________________
#Öffnet Datei, liest zeilen ein und tauscht in ihnen
#jedes Vorkommen von Ich am Anfang der Zeile
#gegen Wir

open (INPUT, "$ARGV[0]") || die "Kann $ARGV[0] nicht öffnen" ;
open (OUTPUT, ">$ARGV[1]") || die "Kann $ARGV[1] nicht anlegen";

while ($input = <INPUT>) {
     $input =~ s/^Ich/Wir/g;
     print OUTPUT $input;
}
________________________________________________

Mehrere Zeichen wechseln:

$input =~ tr/AEIOU/aeiou/;

Zeichenfunktionen

Perl stellt eine Reihe von Funktionen zur Verfügung, die für die Manipulation von Zeichenketten gedacht sind.

chop ($zeichen):
entfernt das letzte Zeichen von $zeichen
length ($zeichen):
gibt die Länge (Anzahl der Zeichen) von $zeichen zurück
lc ($zeichen):
gibt $zeichen in Kleinbuchstaben zurück
uc ($zeichen):
gibt $zeichen in Großbuchstaben zurück
lcfirst ($zeichen):
gibt $zeichen zurück, erster Buchstabe kleingeschrieben
ucfirst ($zeichen):
gibt $zeichen zurück, erster Buchstabe großgeschrieben
index ($zeichen, $substring):
Gibt die Position von $substring in $zeichen zurück. Wenn nicht vorhanden, dann Rückgabewert -1
rindex ($zeichen, $substring):
Wie index, nur beginnt die Suche von rechts
substr ($zeichen, $offset, $laenge):
Extrahiert aus $zeichen an der Stelle $offset einen String der Länge $laenge. Wenn $laenge weggelassen wird, dann Extraktion bis zum Ende von $zeichen

Bsp.:
$input = "Dies ist ein Satz. Er ist kurz.";

$erg = uc ($input); # $erg: "DIES IST EIN SATZ...
$erg = lc ($input); # $erg: "dies ist ein satz...
$erg = length ($zeichen); # $erg = 31
$erg = index ($input, "ist");# $erg = 5, da das erste 'ist' gefunden wird
$erg = rindex ($input, "ist");# $erg = 22. Das zweite 'ist' wird gefunden
$erg = substr ($input,5,13); # $erg = "ist ein Satz. "
$erg = substr ($input,5); # $erg = "ist ein Satz. Er ist kurz."

Reguläre Ausdrücke

Computer kodieren Buchstaben, Zahlen, Satzzeichen, Zeilenwechsel, Tabulatoren jeweils mit einem eigenen Zeichen, Zusammensetzungen solcher Zeichen bilden Zeichenketten ( strings). Reguläre Ausdrücke dienen zur Beschreibung von Zeichenkettenmustern. Man kann mit ihnen Klassen von Zeichenketten beschreiben.
Zur Beschreibung der Klassen werden Metazeichen verwendet. Soll das Metazeichen als einfaches Zeichen einer Zeichenkette verwendet werden, wird ein '\' vorangestellt. Einfachstes Metazeichen ist der Punkt. Er steht für ein beliebiges Zeichen außer dem newline-Zeichen ( \n).

________________________________________________
#Programmhülle für alle weiteren Beispiele
$input = "1 Ich bin's. 2. Ich bins. Daß könntest Du mir glauben! 3)
(Oder?)\n";

#Wenn der Text in $input eine Teilkette enthält, die dem reg. Ausdruck
# genügt, dann zeige diese Teilkette an
if ($input =~ m/(b.n)/) { #Nur noch der Text in Klammern muß
print "Treffer: ". $1; # im folgenden ersetzt werden
} else {
print "Kein Treffer";
}
_______________________________________________

Beliebiger Buchstabe: \w
Bsp.: m/(D\w)/
PROBLEM: Paßt nicht auf Umlaute u ß

Beliebige Zahl: \d
Bsp.: m/(\d )/ Erg.: '1 '
Bsp.: m/(\d\.)/ Erg.: '2.' Der '\' macht aus dem Metazeichen '.' einen einfachen Punkt.

Anfang der Zeile: ^
Bsp.: m/(^\d)/ Erg.: '1'. Paßt nur auf die 1

Ende der Zeile: $
Bsp.: m/(\n$)/ Erg.: '1'.

Gruppe: [ ]
Gruppen können einfache Listen sein:
Bsp.: m/(D[aeou])/ Erg.: 'Da' (paßt auch auf 'Du')

Oder Mengen enthalten:
[a-z] : paßt auf jeden kleinen Buchstaben. Im Dt. so zu ergänzen: [a-zäöüß]
[0-9]: paßt auf jede Ziffer
[1-5]: paßt auf jede Ziffer von 1 bis 5
[A-ZÄÖÜ] paßt auf alle Großbuchstaben
[A-zÄÖÜäöüß] paßt auf jeden Buchstaben. Vergleichbar mir \w

Quantifikatoren:
? ein oder kein Vorkommnis des voranstehenden Zeichens
+ ein oder beliebig viele Vorkommnisse des voranstehenden Zeichens
* null oder beliebig viele Vorkommnisse des voranstehenden Zeichens
{n;m} paßt wenn es mindestens n mal und höchstens m mal vorkommt

Bsp.: (bin'?s) Erg.: "bin's" (Würde auch auf 'bins' passen)
Bsp: (k.+t) Erg.: 'könntest'

Problem der "greediness", der 'Gier' reg. Ausdrücke. Wenn man z.B. alle Zeichen zwischen 'I' und 'n' erfassen möchte (als Treffer wäre "Ich bin" angezielt), dann würde man das so formulieren:
(I.+n)
Das tatsächliche Ergebnis ist aber:
"Ich bin's. 2. Ich bins. Daß könntest Du mir glauben"
Will man das die Suche nach dem ersten'n' beendet wird, muß man ein '?' verwenden:
(I.+?n)
Erg.: Ich bin


Rückbezüge (Backreferences)

Man kann Reg. Ausdrücke auch für Ersetzefunktionen verwenden. Allerdings kann man in solchen Fällen zumeist nicht angeben, wie denn das gefundene Muster lautet. Wenn man es dennoch für einen Rückbezug braucht, dann setzt man den Tel des reg. Ausdrucks, der weiter verwendet werden soll in Klammern. Man kann sich dann mit $1 usw auf diese Muster beziehen.
Bsp.:
________________________________________________

#Hebt alle strings wie 'bin', 'ben' mit doppelten Strichen hervor:
$input = "1 Ich bin's. 2. Ich bins. Daß könntest Du mir glauben! 3) (Oder?)\n";
$input =~ s|(b.n)|--$1--|g;
print $input;
________________________________________________
________________________________________________

#sortiert Datenbankausgabe in übliche Folge:
$input = "Nachname: Klein Vorname: Uta\n";
$input =~ s|Nachname: (\w+) Vorname: (\w+)\n|$2 $1|;
print $input;
________________________________________________

Dieser erste Abschnitt sollte soviel Wissen vermitteln, daß man eine Datei zeilenweise einlesen, den Inhalt der Zeile mit String-Funktionen oder regulären Ausdrücken manipulieren und das Ergebnis in eine neue Datei speichern kann.
Ein weiterer Abschnitt, der beschreibt, wie man aus Texten Wortlisten erstellt und sortiert, soll folgen. Wie immer sind Anregungen, Kritik und Wünsche, was noch behandelt werden soll, sehr willkommen.

ist fortzusetzen ...

Fotis Jannidis

Veröffentlicht am 13.2.1999




Anhang.

Den Perl-Interpreter kann man für alle möglichen Plattformen und als Sourcecode (in C) hier finden: http://www.perl.com
Die Windows 32 Version findet man hier:
http://www.ActiveState.com/ActivePerl

Einen sehr guten und robusten ASCII-Editor, den Programmer's File Editor, der auch noch kostenlos ist, findet man unter:
http://www.lancs.ac.uk/people/cpaap/pfe/

[*] Bezieht sich auf die Position in der ASCII-Tabelle. (Achtung wg. der dt. Sonderzeichen. Ihre Position in der ASCII-Tabelle führt zu eigenartigen Ergebnissen, z.B. ist ö größer als z.