AA_schnipsel-karton_123rf-9152809_maurus.jpg

© maurus, 123rf.com

Schnipseljagd

Erste Schritte mit Regular Expressions

27.08.2011
Computer erleichtern die Arbeit – man muss ihnen nur genau sagen, was sie tun sollen. Mit regulären Ausdrücken beschleunigen Sie das Suchen und Ersetzen von Zeichenketten auf elegante Art.

Neben dem Übertragen und Darstellen von Daten zählt das Suchen und Ersetzen in Textstrukturen und Zeichenketten zu den häufigsten Aktionen beim Umgang mit dem Computer. Bei letzterem helfen die sogenannten regulären Ausdrücke (engl.: "regular expressions"). Deren Konzept umfasst einen komplexen Text- und Zeichenfilter, der ein ein effektives Suchen und Ersetzen in Zeichenketten jeglicher Form ermöglicht – etwa bei Strings in Programmiersprachen, in Ergebnissen von Datenbankabfragen und in Dokumenten als Dateien auf einem Datenträger.

Es spielt dabei keine Rolle, ob die Textdaten strukturiert vorliegen oder nicht – über Erfolg oder Misserfolg entscheidet nur die richtige Formulierung des regulären Ausdrucks (kurz auch Regex oder RE genannt). Allerdings fällt bei strukturierten Dokumentformaten wie CSV, HTML, XML, XSLT und LaTeX der Einsatz der REs meist leichter. Das Regex-Konzept ist weit verbreitet und zeichnet sich durch sehr hohe Stabilität aus. Für die Programmiersprachen Java, Perl, Python, PHP, Ruby, das .NET-Framework und für die Bash gehört es zum Standardumfang.

Die Beschreibung der gesuchten (Zeichen-)Muster folgt bestimmten syntaktischen Regeln, auch Grammatik genannt. Ein Programm wertet diese Grammatik aus und wendet sie auf eine Menge von Zeichen an. Der Rückgabewert umfasst eine Untermenge von Zeichen oder eine Trefferliste. Gibt es keine Übereinstimmung, bleibt diese Liste leer.

Reguläre Ausdrücke formulieren

Bei der Formulierung regulärer Ausdrücke gilt es generell zwei Punkte zu beachten: Erstens hilft es, wenn das Encoding für die Textdaten identisch mit dem des Regex ist. Anderenfalls müssen Sie Umlaute und Sonderzeichen im regulären Ausdruck für das Encoding der Textdaten anpassen. Der zweite Punkt betrifft die Eigenheiten der Regex-Implementierung in der gewählten Programmiersprache: Nicht alle Sprachen unterstützen den POSIX-Standard vollständig und definieren eigene Steuerzeichen.

Grundlagen

Sicherlich haben Sie schon einmal nach einer Person mit einem Namen gesucht, den es in unterschiedlich geschriebenen, aber phonetisch identischen Varianten gibt – etwa nach einem Meier, Schmidt oder Schulze [1]. Die folgenden Beispiele erläutern den Regex-Einsatz anhand dieses Problems. Als Adressbuch dient dabei eine Textdatei, in der Kommas die einzelnen Felder der Einträge trennen (Listing 1). Als Suchwerkzeug für die Kommandozeile kommt grep zum Einsatz.

Listing 1

Fritz Neunmalklug, Am Sterndamm 6, 12401 Berlin, 030 24 58 16
Joachim Mayer, 12 Rue de la Chapelle, CH-1002 Lausanne, 0041 21 67 23 69
Hans Fröhlich, Karlplatz 15, 51111 Köln, 0221 76 34 20
Horst Fischer, Rathausquai 78, 20165 Hamburg, 040 30 19 56 1
Klaus Meier, Mozartweg 7, 01256 Dresden, 0351 58 14 17
Holger maier, Kreuzgasse 15, 86161 Augsburg, 0821 50 23 19

Um Herrn Meier im Adressbuch zu finden, geben Sie Grep zwei Parameter mit: den Namen als Suchmuster und unser Adressbuch als Datei, in der Grep nachsehen soll (Listing 2, Zeile 1). Die Ausgabe umfasst nur eine einzige Zeile (Listing 2, Zeile 2).

Falls Sie sich nicht mehr genau daran erinnern können, ob sich der Gesuchte nun Meier oder Mayer schreibt, erweitern Sie den Grep-Aufruf kurzerhand um den passenden regulären Ausdruck (Listing 2, Zeile 4). Mit der Option -E behandelt Grep das Suchmuster als erweiterten Ausdruck und interpretiert die Steuerzeichen entsprechend. Die Klammern um ei gruppieren die beiden Zeichen e und i, der senkrechte Strich (|) fungiert als Oder-Operator für die beiden Gruppen (ei) und (ay). Die zusätzlichen Klammern um (ei)|(ay) sorgen dafür, dass Grep die beiden Gruppen als kompletten Unterausdruck auswertet.

Listing 2

$ grep Meier adressbuch
Klaus Meier, Mozartweg 7, 01256 Dresden, 0351 58 14 17
$ grep -E "M((ei)|(ay))er" adressbuch
Joachim Mayer, 12 Rue de la Chapelle, CH-1002 Lausanne, 0041 21 67 23 69
Klaus Meier, Mozartweg 7, 01256 Dresden, 0351 58 14 17
$ grep --color -i -E "M.{2}er" adressbuch
Joachim Mayer, 12 Rue de la Chapelle, CH-1002 Lausanne, 0041 21 67 23 69
Klaus Meier, Mozartweg 7, 01256 Dresden, 0351 58 14 17
Holger maier, Kreuzgasse 15, 86161 Augsburg, 0821 50 23 19

Den dritten Eintrag aus dem Adressbuch (Holger maier) unterschlägt Grep bislang, da der Familienname in der Adressdatei fälschlicherweise mit einem Kleinbuchstaben beginnt. Die Option -i ignoriert die Groß- und Kleinschreibung und fördert auch diesen Eintrag zutage.

Da wir aber auch wissen, dass "Meier" aus fünf Buchstaben besteht, vereinfachen wir den Grep-Aufruf so wie in Zeile 8 von Listing 2. Das Muster M.{2}er passt auf alle Zeichenketten, die mit einem M beginnen, gefolgt von zwei beliebigen, alphanumerischen Zeichen (.{2}) und mit der Buchstabenfolge er am Ende. Welche besonderen Zeichen in einem Muster welche Bedeutung haben, zeigt die Tabelle "Besondere Zeichen in regulären Ausdrücken".

Die Grep-Option --color erweist sich im Alltag als sehr nützlich: Sie hebt diejenigen Zeichen im Suchergebnis hervor, auf die das angegebene Muster passt (Abbildung 1) – in unserem Fall also die passenden Familiennamen.

Abbildung 1: Grep mit farbiger Hervorhebung des Suchergebnisses.

Besondere Zeichen in regulären Ausdrücken

Zeichen Bedeutung Beispiel Treffer bei
? vorhergehendes Element ist null oder ein Mal vorhanden (20)?11 2011, 11
+ mindestens eine Wiederholung des vorhergehenden Elements ab+ ab, abb, abbb, …
* keine, eine oder mehrere Wiederholungen des vorhergehenden Elements ab* a, ab, abb, abbb, …
| logisches Oder a|b a, b
. beliebiges einzelnes Zeichen Betr.g Betrag, Betrug, …
^ am Zeilenanfang ^Berlin Zeile beginnt mit Berlin
$ Zeilenende Berlin$ Zeile endet auf Berlin
[...] Bereich [a-fA-F] Zeichen a bis f und A bis F
[^...] Ausschlussbereich [^a-f] alle Zeichen außer a bis f
(...) gruppiert Elemente Loch(streifen|karte) Lochstreifen, Lochkarte
{x,y} Menge {Minimum, Maximum} \w{3,8} mindestens drei und maximal acht alphanumerische Zeichen
\w alphanumerische Zeichen (a-z, A-Z, 0-9 und _) \w+; \w+ zwei Worte, durch Semikolon und ein Leerzeichen voneinander getrennt
\s Leerzeichen (identisch zu \t\n\r\f) \w+\s+\w+ zwei Worte, durch mindestens ein Leerzeichen oder einen Tabulator getrennt
\d Ziffern (Grep: [0-9]) Zeile \d Zeile 0 bis Zeile 9
Um eines der besonderen Zeichen zu finden, müssen Sie es mittels Backslash escapen. Mit \. finden Sie beispielsweise einen Punkt.

Schlangenbeschwörung

Die Programmiersprache Python macht es Ihnen an vielen Stellen sehr leicht. Möchten Sie alle Varianten von Meier aus einer Namensliste herausfischen, dann kodieren Sie das wie in Listing 3 gezeigt. In Zeile 6 wird jeder Eintrag der Namensliste mit den möglichen Varianten verglichen und als Ergebnis lediglich Meier ausgegeben. Zu mayer gibt es keine exakte Entsprechung in der Variantenliste, weswegen Python diesen Eintrag nicht findet.

Listing 3

# -*- coding: utf-8 -*-
namensliste = ["Meier", "Müller", "Schulze", "mayer"]
variantenliste = ["Meier", "Meyer", "Maier", "Mayer"]
for eintrag in namensliste:
        if eintrag in variantenliste:
                print(eintrag)

Komfortabler funktioniert das Ganze mithilfe von Funktionen für Reguläre Ausdrücke. Diese stellt Python über das Modul re ([2],[3]) bereit – das Kürzel steht für "regular expressions". Dieses Modul wird in Listing 4 in der dritten Zeile deklariert, ab diesem Punkt lassen sich die Funktionen aus re im Skript nutzen. In Zeile 5 bereitet die Funktion re.compile() das Muster zunächst vor. Dabei steht im Muster [ae] für eines der Zeichen a und e, sinngemäß dasselbe gilt für [iy]. Das Caret (^) benennt den Wortanfang, das Dollarzeichen ($) das Wortende.

Listing 4

# -*- coding: utf-8 -*-
import re
namensliste = ["Meier", "Müller", "Schulze", "mayer"]
muster = re.compile('^m[ae][iy]er$', re.IGNORECASE)
for eintrag in namensliste:
        if muster.match(eintrag):
                print(eintrag)

Beim zweiten Parameter des Funktionsaufrufs handelt es sich um re.IGNORECASE, eine vordefinierte Konstante aus dem re-Modul. Sie bewirkt, dass beim Vergleich des Musters die Groß- und Kleinschreibung keine Rolle spielt. Die Funktion re.match() nimmt den Vergleich des Musters mit der Zeichenkette vor. Auf die Namensliste angewendet, erhalten Sie bei der Ausgabe in Zeile 8 sowohl Meier als auch mayer. Diese Variante deckt also alle acht möglichen Schreibweisen ab, ohne sie dazu wie in Listing 3 explizit aufzählen zu müssen.

Regexe in Perl

Ein Sprache wie Perl wäre ohne reguläre Ausdrücke gar nicht vorstellbar [4]. Ein Vergleich geschieht hier mit der Funktion m für den Operator "match", beispielsweise so:

$zeichenkette =~ m/Muster/;

Sofern das Muster auf die Zeichenkette passt, fällt das Ergebnis des Vergleichs positiv aus. Für eine fünfstellige Postleitzahl mit erlaubter Null am Anfang passt das Muster \d{5}, da diese aus fünf beliebigen Ziffern besteht (Listing 5).

Mit Länderkennung für die Schweiz und vier Ziffern lautet das Muster CH-[1-9]\d{3}, wobei die erste Ziffer nur aus dem Bereich 1 bis 9 kommen darf. Die Mengenangabe {3} fordert exakt drei weitere, beliebige Ziffern – sowohl bei weniger als auch bei mehr Ziffern schlägt der Vergleich fehl. Eine Prüfung auf CH-1000 fiele andererseits syntaktisch gültig aus.

Für eine zusätzliche Ortsangabe der Form A-4020 Linz genügt die Ergänzung des bisherigen Musters um ein Leerzeichen ("\s") und alphanumerische Zeichen. Der einfache Ausdruck für eine Adresse in Österreich lautet damit A-\d{4}\s[A-Z][a-z]+. Die Schreibweise [A-Z][a-z]+ stellt dabei sicher, dass der Ortsname aus mindestens zwei Buchstaben besteht und mit einem Großbuchstaben beginnt.

Listing 5

#!/usr/bin/perl -w
my ($muster, $postcode);
$postcode = '01256';
$muster = '\d{5}';
if($postcode =~ m/$muster/)
  {print "PLZ-Format ist korrekt: $postcode\n"}

Matches im Netz

Webdesigner und Grafikspezialisten arbeiten viel mit Farbcodes. Häufig kommt dabei eine hexadezimale Schreibweise zum Einsatz, beispielsweise #ff0000 für Rot in der RGB-Notation. Um diese Angabe automatisiert auf grammatikalische Korrektheit zu prüfen, genügt als Muster der reguläre Ausdruck #[a-f\d]{6}. Auf ein Hash-Zeichen folgen sechs Buchstaben aus dem Bereich a bis f oder die Ziffern 0 bis 9 – alle anderen Zeichen repräsentieren keinen richtigen Farbcode. In der Auswertung bietet sich diese Zeile an, beispielsweise in einem CGI-Skript in Perl (Listing 6).

Listing 6

$muster = '#[a-f\d]{6}';
$farbcode =~ m/$muster/i;

Das unscheinbare i am Ende entspricht der Option -i bei Grep und sorgt auch hier dafür, dass sowohl Groß- als auch Kleinbuchstaben gelten. Das deckt alle Varianten des Farbcodes ab. Auch bei Formularen auf Webseiten empfiehlt es sich, die Nutzereingabe zu validieren. Für E-Mail-Adressen schlägt Jan Goyvaerts [5] folgendes Muster vor:

^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6}$

Es passt auf die von der ICANN freigegebenen Top Level Domains von bis zu sechs Buchstaben Länge. Vor dem Klammeraffen (@) erwartet der Ausdruck mindestens ein Zeichen aus der Menge der Buchstaben oder Ziffern beziehungsweise einen Unterstrich, ein Prozentzeichen oder ein Plus oder Minus. Der Domainname besteht aus mindestens einem Buchstaben, einer Ziffer, einem Punkt oder einem Bindestrich. Nach dem Punkt als Trennzeichen (\.) folgt die Top-Level-Domain, welche aus minimal zwei und maximal sechs Buchstaben bestehen darf (etwa de, org, info oder travel).

Nutzen Sie den Ausdruck in einem PHP-Skript (Listing 7), so hilft wieder die Ergänzung um das i am Ende. Zeile 5 des Listings gibt die Trefferliste aus, sofern die E-Mail-Adresse der Spezifikation entspricht. Ein solcher Treffer besagt freilich noch nicht, dass es diese Adresse auch tatsächlich gibt. Diese Prüfung muss gesondert erfolgen, etwa mittels einer Auswertung des Fehlercodes bei der Zustellung einer Nachricht.

Listing 7

<?php
$email = "geheim.agent@unterschlupf.de";
$muster = '/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i';
preg_match($muster, $email, $treffer);
print_r($treffer);
?>

Suchen und Ersetzen

Neben dem Auffinden von Zeichenketten unterstützen reguläre Ausdrücke auch das Ersetzen sowie Translationen, also buchstabenweise Ersetzungen.

Eine Aufgabe von Autoren redaktioneller Beiträge besteht darin, auf eine einheitliche Schreibweise von Begriffen und Abkürzungen zu achten. Per Hand verursacht das einigen Aufwand, komfortabler funktionierte es per Regex. Um etwa alle Vorkommnisse des Begriffes Wifi durch WLAN zu ersetzen, reicht folgende Zeile in Perl:

$string =~ s/Wifi/WLAN/g;

Mit dem Kommando sed wenden Sie die Ersetzung komfortabel auf eine komplette Datei an:

$ sed --quiet "s/Wifi/WLAN/g; w kopie.txt" original.txt

Die Option --quiet unterdrückt dabei Ausgaben auf der Standardausgabe. Der zweite Parameter enthält durch ein Semikolon getrennte Kommandos an Sed. Beim ersten davon handelt es sich um einen regulären Ausdruck zur Textersetzung. Dabei steht das das Kürzel g am Ende des Regex für "global" und sorgt für eine Ersetzung aller Suchtreffer (Wifi), die in der Datei original.txt vorkommen. Den korrigierten Text speichert Sed anschließend mittels des zweiten Kommandos (w für "write") in der Datei kopie.txt, das Originaldokument bleibt unverändert.

Translationen gelingen in Perl mit tr anstelle von s. Um beispielsweise alle Kleinbuchstaben durch Großbuchstaben zu ersetzen, genügt folgender Aufruf:

$string =~ tr/[a-z]/[A-Z]/;

Bei Sed heißt das entsprechende Kommando y und kann nicht nur einzelne Buchstaben, sondern auch ganze Zeichenketten gleicher Länge ersetzen.

Fazit

Reguläre Ausdrücke helfen dabei, komplizierte Aufgaben bei der Verarbeitung von Zeichensequenzen wesentlich zu vereinfachen. Mit vergleichsweise wenig Aufwand lassen sich eine Textsuche und Ersetzung realisieren. Ein tieferes Verständnis dieser Materie vermittelt das als Standardwerk zum Thema geltende "Mastering Regular Expressions" von Jeffrey E.F. Friedl [6]. Daneben eignet sich "Sed und Awk" von Dale Dougherty und Arnold Robbins [7] als weiterer, höchst informativer Lesestoff. 

Glossar

POSIX

Portable Operating System Interface for Unix (DIN/EN/ISO/IEC 9945, [8]). Eine von der IEEE definierte Familie von Interoperabilitätsstandards für Application Programming Interfaces (APIs) und Software-Tools. POSIX ist prinzipiell nicht auf unixoide Systemen beschränkt; so unterstützt beispielsweise auch die Windows-NT-Systemfamilie POSIX bis zu einem gewissen Grad.

ICANN

Internet Corporation for Assigned Names and Numbers. Die 1998 gegründete Organisation entscheidet unter anderem über die Grundlagen der Verwaltung der Top-Level-Domains (.com, .net, .org, .de, …) im Internet [9].

Infos

[1] Häufigste Nachnamen in Deutschland: http://de.wikipedia.org/wiki/Liste_der_häufigsten_Familiennamen_in_Deutschland

[2] "Python Regular Expression HOWTO": http://docs.python.org/howto/regex.html

[3] Modul re (Python): http://docs.python.org/library/re.html

[4] "Perl Regular Expressions": http://www.troubleshooters.com/codecorn/littperl/perlreg.htm

[5] "How to Find or Validate an Email Address": http://www.regular-expressions.info

[6] Regex von A bis Z: Jeffrey E.F. Friedl, "Mastering Regular Expressions", O'Reilly 1996, ISBN 978-0596528126

[7] Regex in Sed und Awk: Dale Dougherty und Arnold Robbins, "Sed und Awk", O'Reilly 1997, ISBN 1-56592-225-5

[8] POSIX-Standard: http://standards.ieee.org/develop/wg/POSIX.html

[9] ICANN: http://www.icann.org/en/tlds/

Der Autor

Frank Hofmann hat Informatik an der TU Chemnitz studiert. Derzeit arbeitet er in Berlin im Open-Source-Expertennetzwerk Büro 2.0 als Dienstleister mit Spezialisierung auf Druck und Satz. Er gehört zur Linux User Group Potsdam (upLUG).

LinuxCommunity kaufen

Einzelne Ausgabe
 
Abonnements
 

Ähnliche Artikel

Kommentare