# Reguläre Ausdrücke

Gib mir einen Punkt, wo ich hintreten kann, und ich bewege die Erde.
Archimedes, griechischer Mathematiker

# Was sind reguläre Ausdrücke?

Regulären Ausdrücken sind Sie sicherlich schon an der einen oder anderen Stelle begegnet – natürlich vorausgesetzt, Sie haben sich schon einmal näher mit einem Computer beschäftigt. In einem Textverarbeitungsprogramm entsprechen reguläre Ausdrücke der »Suchen«- bzw. »Suchen und Ersetzen«-Funktion. Als Suchmuster können Sie dann eine beliebige Zeichenfolge eingeben und als Ersatz eine weitere beliebige Zeichenkette. Wird das Suchmuster gefunden, wird es durch die angegebene Zeichenfolge ersetzt.

Suchmaschinen wie z. B. Google verwenden ebenfalls reguläre Ausdrücke, um ihre Datenbanken zu durchsuchen. Dabei werden die eingegebenen Zeichenketten als Suchmuster verwendet. Sowohl die Suche in einem Textverarbeitungsprogramm als auch mittels einer Suchmaschine bezeichnet mal als literale Suche, d. h., sie suchen nach einem Wort bzw. einer festen Zeichenkette.

Auch das Suchen nach Dateien ist mit der Verwendung eines regulären Ausdrucks vergleichbar. Wenn Sie z. B. Ihre Laufwerke nach allen Dateien durchsuchen möchten, die mit .txt enden, verwenden Sie in der Regel den Suchbegriff *.txt. Das Sternchen steht dabei für eine beliebige Zeichenfolge vor der Zeichenkette .txt. In diesem Fall ist *.txt einem regulären Ausdruck schon sehr ähnlich. Solche Ausdrücke sind Metasuchmuster, da sie sich auf variable Zeichenketten beziehen.

Reguläre Ausdrücke in Perl sind aber nicht nur auf solche »einfache« Suchmuster beschränkt. Sie stellen daher schon fast eine eigene Programmiersprache dar, die jedoch sehr leicht zu erlernen ist.

# Reguläre Ausdrücke verwenden

Generell werden reguläre Ausdrücke bzw. Suchmuster von normalen Schrägstrichen umgeben, also

/Suchmuster/

Einem solchen regulären Ausdruck sind Sie bereits in Abschnitt 6.1, Parameterübergabe, begegnet. Als Suchmuster können Sie nun verschiedene Zeichenfolgen notieren. Ein Beispiel:

#!/usr/bin/perl -w
use CGI qw(:standard);

print header();

my @begriffe = ("Maus","Braut","Haus","Laut");
foreach(@begriffe)
{
  if($_ =~ /aus/)
  {
    print "Treffer!";
  }
}

Listing 8.1: Einfaches Beispiel für die Verwendung eines regulären Ausdrucks

Dieses kurze Beispiel würde zweimal die Zeichenkette Treffer! ausgeben, da die Zeichenkette aus sowohl in Maus als auch in Haus vorkommt.

Die ersten Zeilen des Scripts sollten verständlich sein. Nach der Ausgabe des HTML-Headers folgt die Definition der Liste @begriffe mit vier Elementen. Dann beginnt eine foreach-Schleife, in der jedes Element der zuvor definierten Liste einmal abgearbeitet wird. Der Wert des gerade aktuellen Elements steht in der Kontextvariable $_ zur Verfügung. Neu ist der Operator =~.

if($_ =~ /aus/)

Dies ist der so genannte Bindungsoperator. Der Operator versucht nun, den regulären Ausdruck /aus/ an den Skalar $_ zu binden. War die Bindung erfolgreich, d. h., konnte das Suchmuster im Skalar $_ gefunden werden, liefert die Bedingung true. Passt das Suchmuster nicht, liefert die Bedingung false.

Dies ist ein einfaches literales Suchmuster, denn das Suchmuster würde auch auf mausern, Laus, hausen und Ähnliches passen. Manchmal reicht das jedoch nicht aus, und aus diesem Grund gibt es die so genannten Metazeichen.

# Metazeichen

Die oft auch Wildcard genannten Metazeichen erweitern die Möglichkeit der Suchmuster um variable Zeichen. Anstatt im folgenden Listing nach der festen Zeichenkette est oder ert zu suchen, wird einfach nach e.t gesucht.

#!/usr/bin/perl -w
use CGI qw(:standard);

print header();

my @begriffe = ("Pest","Fest","Test","Wert","Robert","begeistert");
foreach(@begriffe)
{
  if($_ =~ /e.t/)
  {
    print "<b>e.t</b> passt auf $_<br>";
  }
}

Listing 8.2: Verwendung von Metazeichen in regulären Ausdrücken

Dieses Beispiel erzeugt die folgende Ausgabe:

e.t passt auf Pest
e.t passt auf Fest
e.t passt auf Test
e.t passt auf Wert
e.t passt auf Robert
e.t passt auf begeistert

Das Suchmuster e.t passt also auf alle sechs Zeichenketten. Der Grund dafür ist, dass der Punkt im Suchmuster für ein variables Zeichen steht. Dadurch darf als Zeichen zwischen e und t im Suchmuster jedes beliebige Zeichen vorkommen. Da in jedem der sechs Elemente der @begriffe-Liste entweder est oder ert vorkommt, liefert der reguläre Ausdruck also sechs Treffer.

Was aber passiert, wenn in einer Zeichenkette ein Punkt vorkommt, der zusätzlich auch gefunden bzw. berücksichtigt werden soll? Gehen Sie einmal von der Zahl Pi aus.

$pi = "3.1415";

Würden Sie nun z. B. mit dem Suchmuster /3.1415/ suchen, würde das Suchmuster auf 3.1415, aber auch auf 3_1415 passen. Auch auf 31415, da der Punkt in einem Suchmuster für ein variables Zeichen steht. Aus diesem Grund gibt es das Escape-Zeichen \. Es bedeutet, dass das nachfolgende Zeichen als normales Zeichen und nicht als Metazeichen zu interpretieren ist. Das Suchmuster /3\.1415/ würde also nur noch auf 3.1415 passen und nicht mehr auf die Schreibweisen 3_1415 bzw. 31415. Um nun wiederum das Escape-Zeichen zu entwerten und damit eine Suche nach dem Backslash \ zu ermöglichen, müssen Sie einfach zwei Backslashs hintereinander notieren, z. B. \\.

Ein weiteres Metazeichen ist das ^-Zeichen. Das Zeichen, das nach dem ^ folgt, muss am Anfang einer Zeichenkette stehen.

#!/usr/bin/perl -w
use CGI qw(:standard);

print header();

my @begriffe = ("München","Hamburg","Paris","Madrid","Rom","Mailand");
foreach(@begriffe)
{
  if($_ =~ /^M/)
  {
    print "$_ fängt mit M an<br>";
  }
}

Listing 8.3: Suche nach einem M am Anfang eines Strings

Die Ausgabe:

München fängt mit M an
Madrid fängt mit M an
Mailand fängt mit M an

Obwohl auch Hamburg ein M enthält, passt das Suchmuster /^M/ aus zwei Gründen nicht. Der erste ist, dass das M nicht am Anfang des Strings steht, und der zweite, dass das M kleingeschrieben ist. Aus diesem Grund werden als Treffer nur München, Madrid und Mailand ausgegeben.

Mit dem Metazeichen $ können Sie überprüfen, ob ein String auf eine bestimmte Zeichenfolge endet. Würden Sie das Suchmuster in Listing 8.3 durch /d$/ ersetzen, würden als Treffer Madrid und Mailand ausgegeben werden.

# Quantifier

Mit Quantifiern kann man bestimmen, wie oft ein Zeichen eines Suchmusters gefunden werden soll.

Das Suchmuster /haus/ passt auf die Strings haus, nicht aber auf hans oder hannes. Das Suchmuster /ha.s/ würde auf haus und hans passen, nicht aber auf hannes. Durch den +-Quantifier würde das Suchmuster /ha.+s/ auf haus, hans und hannes passen. Der +‑Quantifier bewirkt, dass das zuvor notierte Zeichen (in diesem Fall ein beliebiges) mindestens einmal vorkommen muss und mehrmals vorkommen darf. Ohne den +‑Quantifier hätten Sie das Suchmuster /ha.+s/ folgendermaßen schreiben müssen, damit es auf hannes passt: /ha...s/. Dadurch hätte es aber nicht mehr auf haus gepasst, da der String haus nur 4 Zeichen lang ist, das Suchmuster aber mindestens 6 Zeichen erwartet.

Mit dem ?-Quantifier können Sie festlegen, dass das vorangegangene Zeichen vorkommen darf, aber nicht vorkommen muss. So würde das Suchmuster /aus?/ sowohl auf die Strings Haus und Maus als auch auf Maul oder Maut passen.

Eine Verbindung des +- und ?-Quantifiers ist der *-Quantifier. Damit muss das vorangegangene Zeichen nicht vorkommen, darf aber einmal oder auch mehrmals vorkommen. Das Suchmuster /M.*/ bedeutet dann z. B., dass nach einem großen M nichts folgen muss und ein oder mehrere beliebige Zeichen folgen dürfen. Für die Überprüfung eines Dateinamens, etwa ob dieser auf .htm endet, würde der reguläre Ausdruck also /.+\.htm$/ lauten. Ein Beispiel:

#!/usr/bin/perl -w
use CGI qw(:standard);

print header();

my @begriffe = ("index.htm","start.htm","hallo.html","test.html");
foreach(@begriffe)
{
  if($_ =~ /.*\.htm$/)
  {
    print "das Suchmuster passt auf $_<br>";
  }
}

Listing 8.4: Verwendung von Quantifiern und Metazeichen in regulären Ausdrücken

Die Ausgabe des Listing 8.4

das Suchmuster passt auf index.htm
das Suchmuster passt auf start.htm

Sehen Sie sich das Suchmuster noch einmal genauer an. .+ bedeutet, dass beliebig viele Zeichen am Anfang des Strings stehen dürfen, und es ist außerdem egal, welche Zeichen das sind. Danach muss auf jeden Fall die Zeichenkette .htm folgen. Damit auch wirklich ein Punkt vor dem htm folgt, wird der Punkt mittels Backslash \ entwertet. Das $-Metazeichen bedeutet, dass nach dem .htm kein Zeichen mehr folgen darf. Somit passt das Suchmuster nur auf index.htm und start.htm, nicht aber auf hallo.html und test.html. Damit beide Dateiendungen berücksichtigt werden, also sowohl htm als auch html, müssen Sie das Suchmuster /.+\.html?$/ verwenden.

Diese Quantifier lassen sich aber noch spezieller einsetzen. So ist es möglich, ganz speziell die Häufigkeit eines Zeichens zu bestimmen. Dabei wird hinter einem Zeichen im Suchmuster ein geschwungenes Klammernpaar notiert und als erste Zahl, wie oft das Zeichen mindestens vorkommen soll, und, getrennt durch ein Komma, wie oft das Zeichen maximal vorkommen darf. Das Suchmuster

/a{3,6}/

passt also auf das drei- bis sechsmalige Auftreten des Zeichens a. Folgende Vorkommen würden also zu einem Treffer führen: aaa, aaaa, aaaaa und aaaaaa. Auch wenn das Zeichen a 12-mal hintereinander vorkommt, liefert dieses Suchmuster einen Treffer, immerhin kommt das a trotzdem 3‑, 4-, 5- oder 6-mal hintereinander vor. Nun lässt sich dies aber noch spezieller einschränken. Soll das Zeichen a z. B. genau dreimal vorkommen, dann lassen Sie das Komma und die obere Grenze einfach weg.

/a{3}/

Dieses Suchmuster findet das Zeichen a also genau dreimal hintereinander. Lassen Sie hingegen die obere Grenze weg und notieren trotzdem das Komma, findet das Suchmuster das angegebene Zeichen mindestens so oft wie angegeben. Aber auch größere Vorkommen werden gefunden.

  • /x{2,4}/
    findet jeden String, der xx, xxx und xxxx enthält. Das x muss mindestens zweimal vorkommen, darf aber auch mehr als viermal vorkommen.
  • /x{2,}/
    findet jeden String, der mindestens zwei aufeinander folgende x enthält.
  • /x{2}/
    findet jeden String, der xx enthält.

# Gruppierung

Der Quantifier + findet das zuvor notierte Zeichen mindestens einmal. So würde das Suchmuster /aua+/ sowohl auf aua als auch auf auaa oder auaaa passen. Mit runden Klammern können Sie Muster aber gruppieren, so dass z. B. Quantifier für eine ganze Mustergruppe gelten. Das Suchmuster /(aua)+/ würde dann aua, auaaua und auaauaaua finden. Dies gilt natürlich auch für die anderen Quantifier.

# Zeichenklassen

Sie haben bereits das Metazeichen . kennen gelernt, das ein beliebiges Zeichen repräsentiert. Im Grunde genommen entspricht das .-Metazeichen eigentlich einer Zeichenklasse, die alle möglichen Zeichen enthält. Solche Zeichenklassen können Sie auch selbst definieren. Dazu notieren Sie die Zeichen, die Mitglied dieser Klasse sein sollen, innerhalb von eckigen Klammern.

Das Suchmuster /[abcdef]/ würde also ein Zeichen, das dem Zeichen a, b, c, d, e oder f entspricht. Diese Schreibweise ist eigentlich sehr einfach, es gibt aber auch dafür eine Abkürzung. So können Sie mit dem Bindestrich – einfach einen Bereich nach dem Schema von ... bis angeben. Die Klasse [abcdef] könnte dann auch [a-f] geschrieben werden. Dies fällt vor allem bei der folgenden Klasse auf, die alle alphanumerischen Zeichen enthält:

[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789].

Die verkürzte Schreibweise lautet:

[a-zA-Z0–9].

Möchten Sie z. B. auch nach dem Bindestrich suchen, müssen Sie diesen vorher mit einem Backslash entwerten. Dies gilt auch für den Backslash an sich.

[a-zA-Z0–9\-\\]

Diese Zeichenklasse enthält nun alle Zeichen von a bis z, von A bis Z, von 0 bis 9 und den Bindestrich und den Backslash.

Manchmal ist es jedoch einfacher, die Zeichen anzugeben, die nicht gefunden werden sollen. Zu Beginn der Zeichenklasse müssen Sie dann das ^-Zeichen notieren (beachten Sie, dass dieses Zeichen außerhalb einer Zeichenklasse eine andere Bedeutung besitzt). Das Suchmuster /[^a-f]/ würde also jedes Zeichen finden, das nicht dem a, b, c, d, e oder f entspricht.

# Abkürzungen

Einige Zeichenklassen werden aber so häufig verwendet, dass diese nochmals abgekürzt werden können. Die Zeichenklasse [0–9] ließe sich mit [\d] weiter verkürzen.

Die folgende Tabelle enthält eine Übersicht dieser speziellen Zeichenklassen:

Abkürzung Erklärung
[\d] Findet eine beliebige Ziffer von 0 bis 9, entspricht [09]
[\D] Findet jede Nicht-Ziffer, entspricht [^09]
[\W] Findet jedes Wortzeichen, entspricht [a-zA-Z09_]
[\w] Findet jedes Nicht-Wortzeichen, entspricht [^a-zA-Z0–9_]
[\s] Findet jedes Whitespace-Zeichen, entspricht [\f\t\n\r]
[\S] Findet jedes Nicht-Whitespace-Zeichen, entspricht [^\f\t\n\r]

Tabelle 8.1: Abkürzungen für Zeichenklassen

Whitespace-Zeichen sind nichtdruckbare Zeichen, wie z. B. ein Tabulator \t oder ein Zeilenumbruch \n. Diese Zeichen verändern nur die Ausgabe, egal ob auf dem Bildschirm oder im Ausdruck. Aus diesem Grund werden sie auch Whitespace-Zeichen genannt, weil Sie die Zeichen an sich nicht sehen können.

  • \t steht für den Tabulator.

  • \n steht für einen Zeilenumbruch bzw. Zeilenvorschub.

  • \r steht für den Wagenrücklauf.

  • \f steht für den Formularumbruch bzw. Formularvorschub.

# Alternativen

Mit der Trennlinie | (auch Breakbar oder Pipe genannt) können Sie alternative Suchmuster notieren. Vor der Pipe wird das eigentliche Suchmuster notiert und dahinter die Alternative.

/htm|html/

Wird die Zeichenfolge htm nicht gefunden, wird automatisch nach html gesucht.

# Flags

Mit speziellen Flags, die hinter dem Suchmuster notiert werden (außerhalb der beiden Schrägstriche), können Sie die Suche beeinflussen. So können Sie festlegen, ob die Groß- und Kleinschreibung ignoriert werden soll oder ob die Zeichenketten aus mehreren Zeilen bestehen dürfen.

Flag Erklärung
c Bei einem Fehler wird die Suchposition nicht zurückgesetzt.
g Globale Suche, d. h., alle Vorkommen finden.
i Groß- und Kleinschreibung wird ignoriert.
m Zeichenketten dürfen aus mehreren Zeilen bestehen.
o Das Suchmuster soll nur einmal angewendet werden.
s Die Zeichenketten werden als eine Zeile betrachtet.
x Erweiterte Syntax.

Tabelle 8.2: Flags für Suchmuster

Beispiele:

/haus/i  
  # ignoriert die Groß-/Kleinschreibung, findet also HAUS,   
  # haus, Haus etc.  
/aaa/s  
  # ignoriert Zeilenumbrüche  
/haus/is  
  # ignoriert Groß-/Kleinschreibung und Zeilenumbrüche

# Suchen und Ersetzen

Neben der Möglichkeit, etwas zu suchen, können Sie auch eine Ersetzung der gefundenen Zeichenkette vornehmen. Wurde ein Suchmuster also gefunden, wird es durch die angegebene Zeichenkette ersetzt. Die allgemeine Syntax lautet:

$ersetzt =~ s/Suchmuster/Ersatzzeichenkette/[Flags]

Wichtig ist, dass Sie vor dem gesamten Ausdruck ein kleines s notieren müssen, damit der Suchen-Ersetzen-Vorgang ausgeführt werden kann.

Durch die Suchen-Ersetzen-Möglichkeit haben sich die regulären Ausdrücke in das Herz der Programmierer vorgearbeitet. Diese Möglichkeit ist besonders hilfreich, wenn Sie in HTML-Dokumenten bestimmte Zeichen oder Zeichenfolgen ersetzen möchten, z. B. alle Ä, Ö, Ü, ä, ö, ü und ß durch die entsprechenden Entities oder ein Datum durch ein anderes.

#!/usr/bin/perl -w
use CGI qw(:standard);

print header();

my $datum = "Das letzte Änderungsdatum lautet 10.10.2002";
print "<b>VORHER:</b> $datum<br>";
$datum =~ s/[\d]{2}\.[\d]{2}\.[\d]{4}/23.10.2002/g;
print "<b>NACHHER:</b> $datum";

Listing 8.5: Suchen und Ersetzen mit regulären Ausdrücken

Das Suchmuster /[\d]{2}\.[\d]{2}\.[\d]{4}/ passt auf jedes Datum in dem Format TT.MM.JJJJ. An dieser Stelle können Sie auch die Verbindung von Zeichenklassen und Quantifiern sehen.

In Listing 8.5 wird zu Beginn der Skalar $datum definiert, dem gleichzeitig eine Zeichenkette zugewiesen wird. Anschließend erfolgt die Ausgabe der ursprünglichen Zeichenkette, also bevor sie durch den regulären Ausdruck verändert wurde. Anschließend wird der Skalar $datum mit einem regulären Ausdruck verändert.

$datum =~ s/[\d]{2}\.[\d]{2}\.[\d]{4}/23.10.2002/g;

Der Ausdruck wird mit s eingeleitet, und nach dem ersten Schrägstrich folgt das Suchmuster. Dieses sucht nach einem Datum in der Form TT.MM.JJJJ. Nach dem zweiten Schrägstrich folgt die Ersatzzeichenkette, in diesem Fall wieder ein Datum. Nach dem dritten Schrägstrich folgt das Flag g, was bewirkt, dass alle Treffer im String $datum durch das neue Datum ersetzt werden. Anschließend wird $datum wieder im Browser ausgegeben.

Die Ausgabe lautet:

VORHER: Das letzte Änderungsdatum lautet 10.10.2002
NACHHER: Das letzte Änderungsdatum lautet 23.10.2002

# Zusammenfassung

  • Reguläre Ausdrücke werden in Schrägstrichen /.../ notiert.
  • Um ein Suchmuster auf eine Zeichenkette anwenden zu können, muss der Bindungsoperator =~ verwendet werden.
  • Metazeichen ermöglichen in regulären Ausdrücken die Verwendung von Platzhaltern.
  • Quantifier bestimmen, wie oft ein Zeichen innerhalb einer Zeichenkette vorkommen soll oder darf.
  • Mit runden Klammern können Sie Muster gruppieren und Quantifier somit auf eine ganze Mustergruppen anwenden.
  • Zeichenklassen werden durch eckige Klammern definiert. Für oft verwendete Zeichenklassen gibt es Abkürzungen.
  • Alternative Suchmuster werden mit einem Pipe-Zeichen angegeben.
  • Mit Flags kann die Suche nach einem Muster beeinflusst werden. Diese Flags werden immer hinter dem gesamten Suchmuster notiert (außerhalb der Schrägstriche).
  • Der Suchen-Ersetzen-Vorgang wird durch ein kleines s vor dem Ausdruck eingeleitet. Das Suchmuster und die Ersatzzeichenkette werden durch einen Schrägstrich voneinander getrennt.

# Fragen und Übungen

  1. Was bedeutet das .-Metazeichen?
  2. Mit welchem Zeichen können Sie festlegen, dass das zuvor notierte Zeichen vorkommen kann, aber nicht vorkommen muss?
  3. Welche Zeichenketten findet das Suchmuster /y{3,5}/?
  4. Wie müsste der reguläre Ausdruck lauten, der innerhalb einer Zeichenkette alle kleinen ö durch die Zeichenkette &ouml; ersetzt?
  5. Schreiben Sie ein Perl-Script, das alle deutschen Umlaute (Ä, Ö, Ü, ä, ö, ü), das scharfe S (ß), spitze Klammern (< und >) und das kaufmännische Und (&) in einem HTML-Dokument durch die entsprechenden HTML-Entities ersetzt. Aschließend soll es im Browser ausgegeben werden.