Metazeichen bei egrep

(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)

Wir beginnen mit einigen Metazeichen von egrep, um die Möglichkeiten dieses Werkzeugs auszuloten. Ich werde diese nur kurz mit ein paar Beispielen behandeln; die erschöpfende Beschreibung folgt in späteren Abschnitten.

Typografische Konventionen

Vor Beginn stellen Sie bitte sicher, dass Sie die hier verwendeten typografischen Konventionen kennen (siehe Übersicht zu Reguläre Ausdrücke: Typografische Konventionen).

Zeilenanfang und Zeilenende

Wahrscheinlich am einfachsten zu verstehen sind die Metazeichen ˹^˼ (Zirkumflex) und ˹$˼ (Dollar), die für den Anfang bzw. das Ende der zu prüfenden Zeile stehen. Wie wir gesehen haben, findet der reguläre Ausdruck ˹ding˼ die Zeichen d‧i‧n‧g irgendwo auf der Zeile, aber ˹^ding˼ findet die Zeichen d‧i‧n‧g nur dann, wenn sie am Anfang der Zeile stehen. Analog passt der reguläre Ausdruck ˹ding$˼ nur am Ende der Zeile, wenn zum Beispiel eine Zeile mit Unding endet. Weil Zirkumflex und Dollar den regulären Ausdruck am Anfang oder am Ende der Zeile festmachen, nennt man sie Zeilenanker oder einfach Anker.

Machen Sie es sich zur Gewohnheit, reguläre Ausdrücke in einem wörtlichen Sinn zu interpretieren. Lesen Sie nicht:

˹^ding˼ passt auf eine Zeile mit ding am Anfang

sondern eher:

˹^ding˼ passt auf einen Zeilenanfang, unmittelbar gefolgt von d, unmittelbar gefolgt von i, unmittelbar gefolgt von n, unmittelbar gefolgt von g.

Das kommt zwar auf dasselbe heraus, aber die wörtliche Lesart erlaubt es, einen neuen Ausdruck besser zu verstehen. Wie würde egrep ˹^ding$˼ , ˹^$˼ oder ganz einfach ˹^˼ allein interpretieren? Auflösung siehe Lösung 1.

Der Zirkumflex und das Dollarzeichen sind insofern speziell, als sie eine Position in der Zeile beschreiben und nicht ein Zeichen selbst. Es gibt natürlich einige Möglichkeiten, um auf wirkliche Zeichen in der Zeile zu prüfen. Neben Literalen, die für das Zeichen selbst stehen, gibt es einige andere Metazeichen, die im folgenden behandelt werden.

Zeichenklassen

Eines von mehreren Zeichen erkennen

Nehmen wir an, Sie wollen nach dem String »Birma« suchen, aber auch »Burma« (Anmerkung: Das ist die in der Schweiz und im Englischen übliche Schreibweise) zulassen. Die Konstruktion ˹[...]˼, meist Zeichenklasse genannt, ermöglicht es, eine Liste von Zeichen anzugeben, nach denen gesucht werden soll. Ein ˹i˼ passt nur auf ein i, ein ˹u˼ nur auf ein u, ein ˹iu˼ aber auf beide. Der reguläre Ausdruck ˹B[iu]rma˼ bedeutet also wörtlich: Suche nach B, gefolgt von einem i oder einem u, gefolgt von r, einem m und danach einem a. Ich bin ziemlich schlecht in der Rechtschreibung, daher benutze ich oft reguläre Ausdrücke, um Wörter zu finden, die ich immer wieder falsch schreibe. Einer meiner häufigen Fehler ist ˹sep[ea]r[ea]te˼, weil ich mich nie daran erinnern kann, ob das Wort nun »seperate«, »separate«, »separete« oder wie auch immer geschrieben wird.

Beachten Sie, dass zwischen den Zeichen außerhalb der Zeichenklasse (hier ˹r˼, ˹m˼ und ˹a˼ im vollständigen Ausdruck ˹B[iu]rma˼) ein Nacheinander oder ein »und dann« impliziert wird –- zunächst ein ˹r˼ und dann ein ˹m˼ und dann ein ˹a˼. Ganz anders innerhalb der Zeichenklasse: Dort spielt die Reihenfolge überhaupt keine Rolle, es geht dort um ein oder.

Oder vielleicht wollen Sie die Groß- oder Kleinschreibung eines Wortes zulassen: ˹[Rr]ot˼. Das passt noch immer auch auf Zeilen, bei denen der String rot oder Rot als Teil eines Wortes vorkommt, etwa in Protest. Ich reite etwas auf diesem Punkt herum, weil meine Erfahrung zeigt, dass dies bei Neulingen ein Stolperstein ist. Wenn wir ein paar weitere Metazeichen kennen, werde ich erneut darauf zurückkommen.

Die Liste in den eckigen Klammern kann beliebig viele Zeichen enthalten. Zum Beispiel erkennt ˹[123456]˼ irgendeine der angegebenen Ziffern. Diese Zeichenklasse kann als Teil von ˹<H[123456]>˼ (erkennt <H1>, <H2>, <H3> usw.) nützlich sein, wenn es darum geht, HTML-Tags zu prüfen.

Innerhalb einer Zeichenklasse gibt das Zeichenklassen-Metazeichen ›­‹›­‹›­‹›-‹›­‹ (Bindestrich) einen Bereich von Zeichen an: ˹<H[1­-6]>˼ bedeutet exakt dasselbe wie das vorherige Beispiel. ˹[0­-9]˼ und ˹[a-­z]˼ sind übliche Abkürzungen für Ziffern bzw. Kleinbuchstaben. Mehrfache Bereiche sind zugelassen. ˹[0123456789abcdefABCDEF]˼ kann also kürzer so geschrieben werden: ˹[0­-9a-­fA-­F]˼ (oder auch ˹[A-­Fa­-f0-9]˼, die Reihenfolge der Bereiche spielt keine Rolle). Diese drei Bereiche werden oft gebraucht, wenn man mit hexadezimalen Zahlen arbeitet. Bereiche können auch mit Literalen kombiniert werden: ˹[0-­9A-Z_!.?]˼ passt auf Ziffern, Großbuchstaben, den Unterstrich, Ausrufezeichen, Punkt oder Fragezeichen.

Beachten Sie, dass der Bindestrich nur innerhalb einer Zeichenklasse ein Metazeichen ist –- außerhalb dieser ist er ein normaler Bindestrich. Sogar innerhalb einer Zeichenklasse ist er nicht immer ein Metazeichen. Falls der Bindestrich das erste Zeichen innerhalb der Klasse ist, kann er ja schlecht einen Bereich angeben; er wird dann als Literal behandelt. Ganz ähnlich sind das Fragezeichen und der Punkt in einem regulären Ausdruck normalerweise Metazeichen, aber nicht innerhalb einer Zeichenklasse! Um das ganz klarzumachen: Nur die zwei Bindestriche in ˹[0-­9A-Z_!.?]˼ sind Metazeichen.

Zeichenklassen haben ihre eigene Miniatur-Sprache. Die Regeln, welche Zeichen als Metazeichen gelten (und was deren Funktion ist), sind inner- und außerhalb von Zeichenklassen verschieden.

Weitere Beispiele dazu folgen in Kürze.

Negierte Zeichenklassen

Wenn man ˹[^...]˼ statt ˹[...]˼ benutzt, passt die Klasse auf alle Zeichen, die nicht unter den angegebenen Zeichen sind. Zum Beispiel erkennt ˹[^1-­6]˼ alle Zeichen außer 1 bis 6. Der Zirkumflex als erstes Zeichen in der Klasse »negiert« also die Liste –- statt alle erwünschten Zeichen aufzulisten, gibt man die unerwünschten an.

Sicher ist Ihnen aufgefallen, dass das Zeichen ^ derselbe Zirkumflex ist wie der, der gerade vorhin noch »Zeilenanfang« bedeutete. Das Zeichen ist dasselbe, aber die Bedeutung ist völlig verschieden. Das ist ganz ähnlich wie etwa beim deutschen Wort »Hahn«, das je nach Zusammenhang zwei völlig unterschiedliche Dinge bezeichnen kann (den Mann der Henne oder ein Ventil bei Rohrleitungen). Wir haben schon ein anderes Zeichen gesehen (den Bindestrich), das innerhalb einer Zeichenklasse einen Bereich angibt –- und auch da nur, wenn es nicht das erste Zeichen der Klasse ist. ^ fungiert als Anker außerhalb von Zeichenklassen und als Klassen-Metazeichen innerhalb einer Klasse, aber auch nur, wenn es unmittelbar auf die öffnende eckige Klammer folgt (sonst ist es innerhalb von Klassen kein Spezialzeichen). Keine Angst –- damit haben wir schon den verzwicktesten Sonderfall behandelt, die anderen Metazeichen sind einfacher.

Hier folgt ein Beispiel mit einer englischen Wörterliste. Wie im Deutschen folgt auch im Englischen auf ein q fast immer ein u. Wenn wir also nach Wörtern suchen, die ein q, danach aber etwas anderes als ein u enthalten, und das als regulären Ausdruck formulieren, dann erhalten wir ˹q[^u]˼. Ich habe meine Wörterliste abgesucht und fast keine Ausnahmen gefunden! Und von den wenigen, die ich fand, kannte ich einige nicht:

% egrep 'q[^u]' word.list
Iraqi
Iraqian
miqra
qasida
qintar
qoph
zaqqum
%

Zwei bekannte Wörter allerdings fehlten: »Qantas«, die australische Fluggesellschaft, und »Iraq« (engl. Schreibweise für Irak). Beide sind zwar in meiner Wörterliste word.list vorhanden, aber sie wurden von meinem egrep-Befehl nicht ausgegeben. Warum? Auflösung siehe Lösung 2.

Eine negierte Zeichenklasse bedeutet also »Gefordert ist ein Zeichen, das nicht in der Liste vorkommt« und nicht etwa »Ein Zeichen aus der Liste darf nicht vorkommen«. Das sieht auf den ersten Blick gleich aus, aber das Beispiel mit Iraq zeigt den subtilen Unterschied. Negierte Klassen sollte man am besten als Abkürzungen für normale Klassen ansehen, die alle Zeichen enthalten außer denen, die in den eckigen Klammern angegeben sind.

Auf irgendein Zeichen prüfen: der Punkt

Das Metazeichen ˹.˼ , Punkt, ist eine Abkürzung für die Zeichenklasse, die alle möglichen Zeichen enthält. Es ist häufig praktisch, wenn man ein »irgendwas hier« als Platzhalter in einem regulären Ausdruck braucht. Wenn zum Beispiel nach einem Datum gesucht werden soll, das wie 03/19/76, 03-­19-76 oder gar wie 03.19.76 (Anmerkung: Man sollte Daten nie so angeben, weil dieses Format für die Tage vor dem 13. des Monats in Europa anders interpretiert wird als in den USA. Das Format 1976­-05-­01 nach ISO 8601 ist eindeutig. (Anm. d. Ü.)) aussehen kann, kann man einen expliziten regulären Ausdruck formulieren und als Trennzeichen explizit nur /, -­, und . zwischen den Zahlen zulassen: ˹03[-­./]19[-­./]76˼. Man kann aber kürzer auch einfach ˹03.19.76˼ verwenden.

Mehrere Dinge können hier unklar sein. Zunächst sind die Punkte in ˹03[-­./]19[-­./]76˼ keine Metazeichen, weil sie in einer Zeichenklasse vorkommen (nochmals: die Liste der Metazeichen und deren Bedeutung ist inner- und außerhalb von Zeichenklassen verschieden). Auch die Bindestriche sind hier keine Metazeichen, obwohl sie doch innerhalb von Zeichenklassen meist einen Bereich angeben. Wie erwähnt, verliert ein Bindestrich als erstes Zeichen einer Zeichenklasse seine Spezialfunktion. Falsch wäre in dieser Situation ˹[.­/]˼, hier wäre der Bindestrich ein Zeichenklassen-Metazeichen.

Dagegen sind die Punkte in ˹03.19.76˼ tatsächlich Metazeichen, die für irgendein Zeichen stehen, also auch für den Schrägstrich, den Bindestrich oder den Punkt, also für die Zeichen, die wir erwarten. Es ist aber wichtig, sich klarzumachen, dass der Punkt für jedes beliebige Zeichen stehen kann; der reguläre Ausdruck kann also auch bei ››Losnummer: 19 203319 7639‹‹ passen.

˹03[­./]19[­./]76˼ ist präziser, aber auch mühsamer zu schreiben und zu lesen. ˹03.19.76˼ ist einfacher lesbar, aber wenig spezifisch. Was soll man nun benutzen? Das hängt in hohem Maße von den zu untersuchenden Daten ab oder wenigstens von Ihren Annahmen, wie diese aussehen. Sie müssen die Balance zwischen Ihrem (Vor-)Wissen über den zu untersuchenden Text und der Notwendigkeit finden, den regulären Ausdruck sehr exakt zu formulieren. Wenn Sie wissen, dass in Ihren Daten außer Datumsangaben keine anderen Zahlen (wie Telefonnummern) vorkommen, können Sie getrost ˹03.19.76˼ verwenden.

Alternation

Auf einen von mehreren Unterausdrücken prüfen

Ein sehr praktisches Metazeichen ist ˹|˼, das »oder« bedeutet. Es erlaubt, mehrere reguläre Ausdrücke zu einem einzigen Ausdruck zu kombinieren, der dann zutrifft, wenn einer der Unterausdrücke passt. Wenn zum Beispiel ˹Bob˼ und ˹Robert˼ zwei separate reguläre Ausdrücke sind, dann ist ˹Bob|Robert˼ ein regulärer Ausdruck, der beide enthält und dann passt, wenn zu einem der Ausdrücke ein Treffer gefunden wird. Wenn reguläre Ausdrücke auf diese Art kombiniert werden, heißen die Unterausdrücke Alternativen.

Wenn wir zu unserem ˹B[iu]rma˼-Beispiel zurückgehen, können wir dies auch ˹Birma|Burma˼ oder sogar ˹B(i|u)rma˼ schreiben. Im letzteren Fall dienen die Klammern dazu, die Alternativen örtlich zu beschränken. (Damit haben wir mit den runden Klammern zwei weitere Metazeichen kennengelernt.) Beachten Sie außerdem, dass etwas wie ˹B[i|u]rma˼ ganz falsch wäre– - innerhalb einer Zeichenklasse ist der senkrechte Strich ein ganz normales Zeichen wie ˹i˼ und ˹u˼.

Der reguläre Ausdruck ohne Klammern, ˹Bi|urma˼, würde nur ˹Bi˼ oder ˹urma˼ bedeuten, was ebenfalls nicht das Gewünschte wäre. Die Alternativen reichen weit, aber nicht weiter als die einfassenden Klammern. Ein anderes Beispiel ist ˹(First|1st)[Ss]treet˼. (Anmerkung: Sie erinnern sich an die typografischen Konventionen: »« ist ein sichtbar gemachtes Leerzeichen.) Da sowohl ˹First˼ als auch ˹1st˼ mit einem ˹st˼ enden, kann man dies zu ˹(Fir|1)st[Ss]treet˼ verkürzen, aber das lässt sich nicht so gut lesen. Machen Sie sich die Mühe zu verstehen, dass beide Möglichkeiten dasselbe bedeuten.

Hier ein Beispiel mit verschiedenen Schreibweisen meines Vornamens. Vergleichen Sie die drei folgenden regulären Ausdrücke, die alle das Gleiche bewirken:

˹Jeffrey|Jeffery˼
˹Jeff(rey|ery)˼
˹Jeff(re|er)y˼

Wenn außerdem auch die britischen Varianten berücksichtigt werden sollen, sehen die regulären Ausdrücke wie folgt aus:

˹(Geoff|Jeff)(rey|ery)˼
˹(Geo|Je)ff(rey|ery)˼
˹(Geo|Je)ff(re|er)y˼

Beachten Sie außerdem, dass diese letzten drei Ausdrücke dasselbe bewirken wie der längere (aber einfacher verständliche) ˹Jeffrey|Geoffery|Jeffery|Geoffrey˼. Das sind alles verschiedene Arten, dasselbe auszusagen.

Obwohl die Beispiele ˹B[iu]rma˼ und ˹B(i|u)rma˼ das nahelegen könnten, sind Zeichenklassen und Alternationen ganz und gar nicht dasselbe. Eine Zeichenklasse repräsentiert genau ein Zeichen im zu untersuchenden Text. Bei einer Alternation kann dagegen jede Alternative ein ausgewachsener regulärer Ausdruck sein. Zeichenklassen sind so etwas wie Mini-Sprachen innerhalb von regulären Ausdrücken (mit ihrer eigenen Auffassung darüber, was Metazeichen sind), während die Alternation Teil der »eigentlichen« Sprache der regulären Ausdrücke ist. Beide haben ihren Platz, und beide sind sehr nützlich.

Etwas Vorsicht ist bei der Verwendung der Zeilenanker ^ und $ geboten. Man vergleiche ˹^From|Subject|Date:˼ und ˹^(From|Subject|Date):˼. Beide erinnern an unser E-Mail-Beispiel, aber sie verhalten sich ganz unterschiedlich. Der erste Ausdruck besteht aus drei Alternativen und wird dann passen, wenn »˹^From˼ oder ˹Subject˼ oder ˹Date:˼« gefunden wird -– nicht besonders nützlich. Der Zirkumflex am Anfang und das ˹:˼ am Ende soll bei allen drei Alternativen vorkommen. Mit den Klammern können wir die Alternation »beschränken«:

˹^(From|Subject|Date):˼

Damit wird gefunden:

       1) ein Zeilenanfang, gefolgt von F‧r‧o‧m, gefolgt von ››:
oder 2) ein Zeilenanfang, gefolgt von S‧u‧b‧j‧e‧c‧t, gefolgt von ››:‹‹
oder 3) ein Zeilenanfang, gefolgt von D‧a‧t‧e, gefolgt von ››:

Etwas weniger umständlich ausgedrückt: Es werden die Zeilen gefunden, die mit ››From:‹‹, ››Subject: oder ››Date:‹‹ beginnen; und das ist es, was wir wollen, wenn wir relevante Informationen aus einer E-Mail-Datei extrahieren möchten. Zum Beispiel:

% egrep '^(From|Subject|Date): ' mailbox
From: elvis@tabloid.org (The King)
Subject: be seein' ya around
Date: Mon, 11 Jun 2007 11:04:13
From: The Prez <president@whitehouse.gov>
Date: Wed, 13 Jun 2007 8:36:24
Subject: now, about your vote...
...

Groß- und Kleinschreibung ignorieren

Diese E-Mail-Kopfzeilen sind ein guter Anlass, um das Thema der Groß- und Kleinschreibung einzuführen. Normalerweise werden die Schlüsselwörter in den Header-Zeilen mit einem Großbuchstaben am Anfang geschrieben, also beispielsweise »Subject« und »From«, aber der E-Mail-Standard erlaubt auch die durchgehende Großschreibung wie »DATE« und jede beliebige Mischung von Groß- und Kleinbuchstaben. Mit den regulären Ausdrücken aus dem vorherigen Abschnitt erwischen wir diese leider nicht.

Wir könnten statt ˹From˼ so etwas wie [Ff][Rr][Oo][Mm] schreiben und würden so jede mögliche Schreibweise von »From« abdecken, aber das wäre schon sehr mühsam. Deshalb kennt egrep eine spezielle Option, mit der man das Programm instruiert, beim Vergleich von Zeichen die Groß- und Kleinschreibung nicht zu beachten. Diese Option gehört nicht direkt zu den regulären Ausdrücken, aber fast jedes Programm, das reguläre Ausdrücke verwenden kann, hat eine entsprechende Funktion. Bei egrep heißt die Option »-­i«:

% egrep ­-i '^(From|Subject|Date): ' mailbox

Damit erhalten wir die gleichen Zeilen wie vorhin, aber zusätzlich auch noch Zeilen wie:

SUBJECT: MAKE MONEY FAST

Ich verwende die Option -­i sehr häug (vielleicht hängt das ja mit der Anekdote in Lösung 2 zusammen!) und empfehle deshalb, sich den Gebrauch zu merken. Wir werden in späteren Kapiteln noch auf andere Optionen dieser Art treffen.

Wortgrenzen

Häufig entsteht das Problem, dass ein regulärer Ausdruck ein gewünschtes Wort als Teil eines anderen, nicht gewünschten Wortes findet. Wir haben das bei ding innerhalb von bedingen und bei rot innerhalb von Protest gesehen. Obwohl ich gesagt habe, dass egrep im Allgemeinen nichts von übergeordneten Konzepten wie Wörtern versteht, gibt es doch Versionen von egrep, die ein rudimentäres Verständnis dafür haben: Sie können Wortgrenzen erkennen (wo ein Wort beginnt und wo es aufhört).

Dafür benutzen Sie die etwas merkwürdig aussehenden Metasequenzen ˹\<˼ und ˹\>˼, falls Ihr egrep diese unterstützt (nicht alle Versionen tun das). Das sind Anker wie ˹^˼ und ˹$˼, nur dass sie sich auf Wortanfänge und Wortenden beziehen statt auf Zeilenanfänge und -enden. Genau wie die Anker Zirkumflex und Dollar »verbrauchen« sie keine Zeichen. Der Ausdruck ˹\<ding\>˼ besagt wörtlich: »Finde eine Position an einem Wortanfang, unmittelbar gefolgt von den Zeichen d‧i‧n‧g, gefolgt von einer Position am Ende eines Wortes.« Verständlicher ausgedrückt: »Finde das Wort ding.« Mit ˹\<ding˼ bzw. ˹ding\>˼ könnte man nach Wörtern suchen, die mit ding beginnen oder enden.

Beachten Sie, dass ˹<˼ und ˹>˼ für sich keine Metazeichen sind. Erst wenn davor ein Backslash steht, wird die Sequenz zu etwas Besonderem. Darum nennt man sie »Metasequenzen«. Die besondere Interpretation dieser Sequenzen oder Zeichen ist wichtig, nicht die Tatsache, dass manche aus einem Zeichen und andere aus mehreren bestehen; daher verwende ich die zwei Meta-Begriffe meist als Synonyme.

Wie erwähnt, beherrschen nicht alle egrep-Programme die Wortgrenzen-Metazeichen, und auch diejenigen, die es tun, haben nicht plötzlich ein magisches Verständnis für die deutsche (oder englische) Sprache. Ein »Wortanfang« im Sinne von egrep ist einfach eine Position, an der eine Serie von alphanumerischen Zeichen beginnt. Ein »Wortende« wird an der Position gefunden, wo eine solche Serie endet. Die folgende Abbildung markiert solche Positionen an einer Beispielzeile.

Anfang und Ende von Wort-Positionen im Götz-Zitat

Abbildung: Anfang und Ende von »Wort«-Positionen im Götz-Zitat.

Die Wortanfänge (im Sinne von egrep) sind mit nach oben gerichteten Pfeilen markiert; die Wortenden mit Pfeilen nach unten. Wie man sieht, sollte »Wortanfang und -ende« präziser als »Anfang und Ende einer alphanumerischen Zeichensequenz« bezeichnet werden, aber das ist dann vielleicht doch etwas zu viel des Guten.

Kurze Rekapitulation

Tabelle: Zusammenfassung der bisher aufgetretenen Metazeichen.

Metazeichen Name Bedeutung
. Punkt irgendein Zeichen
[...] Zeichenklasse irgendein Zeichen aus der Liste
[^...] negierte Zeichenklasse irgendein Zeichen nicht aus der Liste
^ Zirkumflex, »Hütchen« die Position am Zeilenanfang
$ Dollar die Position am Zeilenende
\< Backslash Kleiner-als die Position am Wortanfang (nicht von allen egrep-Versionen unterstützt)
\> Backslash Größer-als die Position am Wortende (nicht von allen egrep-Versionen unterstützt)
| »oder«, »Pipe« trennt Alternativen in Alternationen
(...) (runde) Klammern beschränken die Reichweite von ˹|˼ (weitere Bedeutungen später)

Zusätzlich zur Tabelle sollten Sie sich Folgendes merken:

  • Die Regeln, was ein Metazeichen ausmacht (und was es exakt bedeutet), sind inner- und außerhalb von Zeichenklassen unterschiedlich. Zum Beispiel ist ein Punkt außerhalb einer Zeichenklasse ein Metazeichen, aber nicht innerhalb. Umgekehrt ist der Bindestrich innerhalb einer Klasse ein Metazeichen, aber nicht außerhalb. Der Zirkumflex hat eine spezielle Bedeutung außerhalb und eine andere, ebenfalls spezielle Bedeutung als erstes Zeichen innerhalb einer Zeichenklasse.
  • Alternationen und Zeichenklassen sind nicht dasselbe. Die Klasse ˹[abc]˼ und die Alternation ˹(a|b|c)˼ bedeuten im Endeffekt dasselbe, aber diese Ähnlichkeit gilt nicht generell. Eine Zeichenklasse erkennt exakt ein Zeichen, egal wie lang oder wie kurz die Liste der möglichen Zeichen ist. Eine Alternation dagegen kann beliebig lange Alternativen besitzen, und die Alternativen brauchen keinerlei Verwandtschaft untereinander zu haben: ˹\<(1000000|eineMillion|tausendTausender)\>˼. Alternationen können nicht wie Zeichenklassen negiert werden.
  • Eine negierte Zeichenklasse ist einfach eine Notation für eine normale Klasse, die alle außer den aufgelisteten Zeichen enthält. ˹[^x]˼ bedeutet nicht »passt, falls kein x vorkommt «, sondern eher »passt auf ein Zeichen, das kein x ist«. Der Unterschied scheint minimal zu sein, ist aber wichtig. Die erste –- falsche -– Interpretation würde eine Leerzeile erkennen, die in Wirklichkeit nicht auf den regulären Ausdruck ˹[^x]˼ passt.
  • Mit der nützlichen Option -­i kann man egrep anweisen, die Groß- und Kleinschreibung zu ignorieren.

Damit kann man schon ganz hübsche Sachen machen, aber so richtig geht es erst mit den optionalen und zählenden Elementen zur Sache.

Optionale Elemente

Wenn wir fast identische Wörter wie Marlen und Maren vergleichen, sehen wir, dass sie sich nur in dem einmal fehlenden l unterscheiden. Der reguläre Ausdruck ˹Marl?en˼ passt auf beide. Das Metazeichen ˹?˼ (Fragezeichen) bedeutet optional. Das Fragezeichen wird hinter das zu untersuchende Zeichen gesetzt. Wenn das Zeichen im zu prüfenden Text vorkommt, ist das okay, wenn es fehlt, passt der reguläre Ausdruck auch.

Das Fragezeichen bezieht sich -– im Unterschied zu den bisher behandelten Metazeichen –- nur auf das unmittelbar vorausgehende Element. ˹Marl?en˼ wird daher so interpretiert: »Erst ˹M˼, dann ein ˹a˼, dann ein r, dann ein ˹l?˼, dann ein ˹e˼ und schließlich ein ˹n˼

Das Fragment ˹l?˼ wird immer passen: Manchmal wird es ein tatsächliches l im Text erkennen und manchmal nicht. Der Punkt bei optionalen Elementen ist der, dass der reguläre Ausdruck »funktioniert«, ob das Element nun passt oder nicht. Das bedeutet überhaupt nicht, dass ein regulärer Ausdruck mit einem ? immer passen würde. So würde beim Text ›Marlon‹ sowohl ˹Mar˼ als auch ˹l?˼ gefunden, aber das o danach verhindert, dass der ganze reguläre Ausdruck den Text erkennt.

Ein anderes Beispiel: Wie endet man die verschiedenen Schreibweisen für den 4. Juli, den amerikanischen Nationalfeiertag? Geschrieben wird das »July 4th«, »July fourth« oder auch nur »Jul 4«. Wir könnten natürlich alle Kombinationen explizit aufzählen und erhielten ˹(July|Jul)(fourth|4th|4)˼, aber lassen Sie mich andere Möglichkeiten darlegen, die dasselbe aussagen.

Zunächst können wir ˹(July|Jul)˼ zu ˹(July? vereinfachen. Ist Ihnen klar, warum das aufs Gleiche hinausläuft? Das Entfernen des ˹|˼ bedeutet auch, dass die Klammern nicht mehr gebraucht werden. Sie zu belassen würde zwar nichts schaden, aber July? ist ohne Klammern schon deutlich lesbarer. Also haben wir jetzt ˹July?(fourth|4th|4)˼.

Auch im zweiten Teil lässt sich ˹4th|4˼ zu ˹4(th) vereinfachen. Wie man sieht, kann sich das ˹?˼ auch auf einen geklammerten Ausdruck beziehen. Innerhalb der Klammern kann es so kompliziert zugehen wie nur denkbar, aber von außen gesehen ist die ganze Klammer eine Einheit. Dies ist einer der häufigeren Verwendungszwecke für runde Klammern: Sie bilden Gruppen für das ˹?˼-Metazeichen (und für ähnliche Zeichen, die ich nächstens vorstelle).

Unser Ausdruck sieht nun so aus: ˹July?(fourth|4(th)?)˼. Obwohl hier ziemlich viele Metazeichen und sogar verschachtelte Klammern auftreten, ist das Ganze noch gut lesbar und nicht schwer zu dechiffrieren. Diese Besprechung von zwei eigentlich simplen Beispielen ist recht lang geworden, aber nebenbei haben wir angrenzende Gebiete gestreift, die viel zum Verständnis von regulären Ausdrücken beitragen - vielleicht auch nur unbewusst. Wir haben dabei Erfahrungen gesammelt, wie man das gleiche Problem auf verschiedene Arten anpacken kann. In den späteren Kapiteln dieses Buches werden Sie noch viele Stellen vorfinden, an denen zur optimalen Problemlösung Kreativität gefragt ist. Das Schreiben eines regulären Ausdrucks hat oft mehr mit Kunst als mit trockener Wissenschaft zu tun.

Andere Quantoren: Repetition

Eine ähnliche Funktion wie das Fragezeichen haben ˹+˼ (Plus) und ˹*˼ (Sternchen oder der Kürze halber einfach Stern). Das Metazeichen ˹+˼ bedeutet »ein oder mehr des unmittelbar Vorausgehenden«, und ˹*˼ bedeutet »eine beliebige Anzahl, inklusive null, des unmittelbar Vorausgehenden«. Mit anderen Worten bedeutet ˹...*˼: »Versuche das Vorausgehende so häufig wie möglich zu erkennen, aber es ist auch gut, wenn es gar nicht gefunden wird.« Die Variante mit dem Pluszeichen, ˹...+˼, ist insofern ähnlich, als auch hier versucht wird, so viele Zeichen wie möglich zu erkennen. Allerdings muss hier das vorausgehende Zeichen mindestens einmal vorkommen. Die drei Metazeichen Fragezeichen, Pluszeichen und Stern nennen wir Quantoren (engl. quantier), weil sie sich auf die Anzahl, das Quantum, der vorausgehenden Zeichen beziehen.

Wie bei ˹...?˼ wird das Element ˹...*˼ immer passen, die Frage ist nur, wie viele Zeichen des zu prüfenden Textes damit erkannt werden. Im Gegensatz dazu muss bei ˹...+˼ das vorausgehende Zeichen im Text mindestens einmal tatsächlich vorkommen.

Zum Beispiel erlaubt ˹ ein Leerzeichen oder auch keines, und ˹ erlaubt jede beliebige Anzahl von Leerzeichen. Wir können das benutzen, um das <H[1­-6]>-Beispiel flexibler zu formulieren. Die HTML-Spezifikation (Anmerkung: Falls Sie mit HTML nicht vertraut sind, macht das fast gar nichts. Ich benutze HTML als typisches Beispiel aus der Praxis, und ich erläutere dazu so viel, wie zum Verständnis der regulären Ausdrücke nötig ist. HTML-Experten werden mehr Subtilitäten in den Beispielen entdecken, aber um solche Details geht es in dieser Einführung noch nicht.) besagt, dass Leerzeichen unmittelbar vor dem schließenden > erlaubt sind, wie in <H3> und <H4●●●>. Wenn wir in unseren regulären Ausdruck ˹ da einsetzen, wo Leerzeichen erlaubt (aber nicht vorgeschrieben) sind, erhalten wir ˹<H[1­-6]*>˼. Der Ausdruck erkennt noch immer <H1>, weil die Leerzeichen nicht erzwungen werden, er ist aber flexibler, weil er außerdem auch die anderen Varianten erkennt.

Als weiteres HTML-Tag betrachten wir <HRSIZE=14>, das einen 14 Pixel dicken horizontalen Balken (»Horizontal Rule«) spezifiziert. Wie beim <H3>-Beispiel vorhin sind auch hier Leerzeichen vor der schließenden spitzen Klammer zulässig. Außerdem sind Leerzeichen links und rechts vom Gleichheitszeichen erlaubt. Mindestens ein Leerzeichen muss zwischen HR und SIZE stehen, mehrere sind erlaubt. Für diese letzte Forderung könnten wir ˹●● schreiben, aber wir werden ˹ benutzen. Das Pluszeichen fordert mindestens ein Leerzeichen, erlaubt aber mehrere. Ist Ihnen klar, warum das dasselbe erkennt wie ˹●●? Das ergibt den regulären Ausdruck ˹<HR+ SIZE*= *14*.

Damit ist unser regulärer Ausdruck flexibler in Bezug auf Leerzeichen, aber noch immer etwas stur bezüglich der Dicke des Balkens. Statt nur Tags mit einer ganz bestimmten Dicke (14) möchten wir Balken jeder Dicke finden. Dazu ersetzen wir ˹14˼ durch einen Ausdruck, der eine beliebige ganze Zahl beschreibt. Nun, eine Zahl besteht aus einer oder mehreren Ziffern, und eine Ziffer lässt sich mit ˹[0­9]˼ beschreiben. »Eine oder mehrere« können wir mit dem Pluszeichen ausdrücken. Also ersetzen wir ˹14˼ durch ˹[0­-9]+˼. Wie man sieht, ist eine Zeichenklasse eine »Einheit«, der direkt ein Plus, ein Fragezeichen usw. nachgestellt werden kann, ohne dass man sie in runde Klammern einfassen muss.

Damit erhalten wir ˹<HR+ SIZE *= * [0­-9]+* - schon ganz schön kompliziert, auch wenn ich hier mit Halbfettdruck, mit kleinen Abständen zwischen den Zeichengruppen und dem »sichtbaren Leerzeichen« ›‹ sehr nachgeholfen habe! (Immerhin kennt egrep die Option -­i, mit der die Groß- und Kleinschreibung ignoriert wird, sonst hätte ich ˹[Hh][Rr]˼ statt ˹HR˼ schreiben müssen.) Der nackte reguläre Ausdruck ˹<HR + SIZE *= *[0­-9]+*>˼ ist zweifellos noch verwirrender. Dieser reguläre Ausdruck sieht vor allem deshalb ungewohnt aus, weil sich die meisten Sterne und Pluszeichen auf das Leerzeichen davor beziehen, und unsere Augen sind darauf konditioniert, Leerraum in Texten gesondert zu behandeln. Da muss man sich beim Lesen von regulären Ausdrücken etwas umstellen, weil bei regulären Ausdrücken das Leerzeichen keine besondere Bedeutung hat. Es ist konzeptionell nicht verschieden von, sagen wir mal, j oder 4. (In späteren Kapiteln werden wir sehen, dass man manche Programme so konfigurieren kann, dass sie Leerzeichen in regulären Ausdrücken nicht beachten, aber egrep gehört nicht zu diesen.)

Das Beispiel ist aber noch nicht ausgereizt. Die Dickenangabe, das Attribut SIZE, ist beim HR-Tag optional, man kann auch ganz einfach <HR> schreiben und bekommt so einen Balken mit vorbestimmter Dicke (zusätzliche Leerzeichen vor dem > sind wie immer erlaubt). Wie sieht ein regulärer Ausdruck aus, der beide Formen zulässt? Tipp: Es ist wichtig zu verstehen, dass der Teil mit der Größenangabe optional ist. Auflösung siehe Lösung 3.

Die Antwort zur vorhergehenden Aufgabe verdient einige Beachtung. Sie zeigt, wie die Quantoren Fragezeichen, Stern und Plus zusammenspielen und wie sie in der Praxis benutzt werden. Die folgende Tabelle fasst ihre Bedeutungen zusammen.

Jeder Quantor fordert eine minimale Anzahl an Treffern, damit der reguläre Ausdruck passt, und eine maximale Anzahl an Versuchen. Bei manchen ist das Minimum null, bei manchen ist das Maximum unendlich.

Tabelle: Zusammenfassung der Quantoren »Repetitions-Metazeichen«.

Minimum Maximum Bedeutung
? null 1 Einmal erlaubt, keinmal möglich (‹›kein- oder einmal‹›‹)
* null kein Limit Jede Anzahl (auch null) erlaubt (‹›keinmal, einmal oder mehrmals›‹)
+ 1 kein Limit Einmal erlaubt, mehrfach möglich (‹››einmal oder mehrmals›‹)

Explizites Minimum und Maximum: Intervalle

Manche egrep-Versionen unterstützen eine Metasequenz, mit der man Zahlen für das erlaubte Minimum und Maximum angeben kann: ˹...{min, max. Dies ist der Intervall-Quantor. Zum Beispiel findet ˹...{3,12}˼ bis zu zwölf Dinge der vorher angegebenen Art, aber es müssen mindestens drei vorhanden sein. Der Ausdruck ˹[a-­zA­-Z]{1,5}˼ würde auf alle Aktientitel an der Börse von New York passen (1 bis 5 Buchstaben). Mit dieser Notation bedeutet {0,1} nichts anderes als das Fragezeichen.

Nur wenige Versionen von egrep unterstützen diese Notation, aber viele der gewichtigeren Programme kennen sie. Ich werde sie daher erst unter Features und Dialekte genauer behandeln, wenn wir das breite Spektrum von Metazeichen betrachten, das heutige Werkzeuge anbieten.

Klammern und Rückwärtsreferenzen

Bis jetzt haben wir Klammern auf zwei Arten verwendet: um den Geltungsbereich einer Alternation einzuschränken und um Gruppen zu bilden, auf die wir Quantoren wie das Fragezeichen oder den Stern anwenden können. Ich möchte hier eine dritte Verwendung anführen, die zwar bei egrep häufig nicht verwendet werden kann (immerhin unterstützt die weitverbreitete GNU-Version von egrep diese), die aber bei anderen Werkzeugen sehr oft gebraucht wird.

Bei vielen Implementationen von regulären Ausdrücken können sich Klammern an den Text »erinnern«, der auf den Unterausdruck gepasst hat, den sie einschließen. Wir werden diese Eigenschaft für eine erste Lösung des Problems mit den verdoppelten Wörtern vom Anfang dieses Kapitels benutzen. Wenn uns das das zu suchende verdoppelte Wort schon bekannt ist (wie »das« weiter vorne in diesem Satz -– haben Sie's gemerkt?), dann können wir explizit danach suchen: ˹dasdas˼. In diesem Fall würden wir (vorausgesetzt, wir benutzen die ­-i-Option) auch dasDasein finden. Das ließe sich mit einem egrep vermeiden, das die Metasequenzen für Wortgrenzen (˹\<...\>˼) kennt: ˹\<dasdas>˼. Wir könnten auch ˹ statt des Leerzeichens benutzen.

Auf diese Art nach allen möglichen verdoppelten Wörtern zu suchen wäre allerdings unmöglich. Nachdem wir ein Wort gefunden haben, wäre es schön, wenn wir irgendwie ausdrücken könnten: »Und jetzt suche dasselbe Wort nochmals!«. Falls Ihr egrep Rückwärtsreferenzen unterstützt, können Sie das. Rückwärtsreferenzen sind eine Möglichkeit, nochmals nach dem gleichen Text zu suchen, der weiter vorne im regulären Ausdruck schon einmal gefunden wurde, ohne dass der Text explizit bekannt sein muss.

Wir beginnen mit ˹\<das+das\>˼ und ersetzen das erste ˹das˼ durch einen regulären Ausdruck, der auf ein beliebiges Wort passt, sagen wir mal ˹[A­-Za­z]+˼. Diesen Ausdruck klammern wir ein –- aus Gründen, die bald klar werden. Außerdem ersetzen wir das zweite ››das‹‹ durch die neue Metasequenz ˹\1˼. Das ergibt ˹\<([A­-Za-­z]+)+\1\>˼.

Werkzeuge, die Rückwärtsreferenzen unterstützen, »erinnern« sich an den Text, der auf einen Unterausdruck innerhalb eines Klammerpaares gepasst hat, und die Metasequenz ˹\1˼ repräsentiert genau diesen Text weiter hinten im regulären Ausdruck.

Das geht natürlich auch mit mehreren Klammerpaaren. Mit ˹\1˼, ˹\2˼, ˹\3˼ usw. kann man auf den Text aus dem ersten, zweiten, dritten usw. Klammerpaar zurückgreifen. Beim Nummerieren der Klammerpaare zählt man die öffnenden Klammern von links nach rechts, bei ˹([a-­z])([0-­9])\1\2˼ bezieht sich das ˹\1˼ auf den von ˹[a­-z]˼ erkannten Text, und ˹\2˼ bezieht sich auf den von ˹[0­-9]˼.

Bei unserem ››dasdas‹-Beispiel passt der Unterausdruck ˹[A­-Za-­z]+˼ auf das erste das. Der Unterausdruck ist eingeklammert, damit wird dieses ››das‹‹ durch das Metazeichen ˹\1˼ zugänglich. Falls das darauffolgende ˹ gefunden wird, wird nicht nach irgendeinem Wort gesucht, sondern nach dem Wort, das in ˹\1˼baufbewahrt ist. Wenn dieses zweite, gleiche Wort gefunden wird, wird mit ˹\>˼ noch auf ein Wortende getestet (damit nicht Wortteile wie bei dasDasein gefunden werden). Nur wenn auch dieser Test wahr ist, passt der ganze reguläre Ausdruck. Einen regulären Ausdruck, der der Problemstellung immer gerecht wird, können wir sowieso nicht finden, weil es ja durchaus Sätze gibt, in denen verdoppelte Wörter (wie das »der« in diesem Satz) korrekt sind. Wenn die verdächtigen Zeilen ausgegeben werden, sieht man sehr schnell, wo etwas nicht stimmt.

Als ich dieses Beispiel entworfen habe, habe ich es natürlich ausprobiert, und zwar an dem Text dieses Buches, den ich bis dahin geschrieben hatte. (Ich verwendete eine egrep-Version, die sowohl ˹\<...\>˼ als auch Rückwärtsreferenzen beherrscht.) Um den Test noch etwas nützlicher zu machen, damit ›Dasdas‹‹ auch gefunden würde, habe ich die erwähnte ­-i-Option angegeben, um die Groß- und Kleinschreibung zu ignorieren: (Anmerkung: Bei bestimmten Versionen von egrep, auch GNU-egrep, muss allerdings der von der Rückwärtsreferenz erkannte Text auch bezüglich der Groß- und Kleinschreibung gleich dem Text aus dem entsprechenden Klammerpaar sein, auch dann, wenn die ­-i-Option benutzt wird. Mit der egrep-Zeile wird also sowohl »das das« als auch »Das Das« gefunden, nicht aber »Das das«, wie das zum Beispiel Perl tun würde.)

% egrep ­i '\<([a­z]+) +\1\>' datei ...

Es ist mir ein bisschen peinlich, aber ich habe im ganzen Buch vierzehn falsche Paare von ››verdoppeltenverdoppelten‹‹ Wörtern gefunden!

So nützlich dieser reguläre Ausdruck ist, so wichtig ist es auch, seine Grenzen zu kennen. Weil egrep jede Zeile für sich allein betrachtet, ist es nicht möglich, verdoppelte Wörter zu finden, die sich auf zwei Zeilen verteilen (eines am Ende der ersten, das verdoppelte am Anfang der nächsten Zeile). Gerade diese sind aber besonders unauffällig. Um auch diesen Fall zu behandeln, braucht es mächtigere Instrumente als egrep; Sie werden unter Erweiterte einführende Beispiele welche kennenlernen.

Ausbrecher!

Einen wichtigen Punkt habe ich noch nicht angesprochen: wie man nach einem Zeichen sucht, das normalerweise in einem regulären Ausdruck als Metazeichen interpretiert würde. Wenn ich zum Beispiel nach einem Internet-Hostnamen wie ega.att.com mit ˹ega.att.com˼ suchte, fände der reguläre Ausdruck auch Dinge wie megawattcomputing. Wie wir wissen, ist ˹.˼ ein Metazeichen, das auf jedes Zeichen passt.

Die Metasequenz, um nach einem wirklichen Punkt zu suchen, ist ein Punkt mit einem Backslash davor: ˹ega\.att\.com˼. Der Backslash wird in diesem Zusammenhang auch Escape (Ausbruch) genannt, weil aus der üblichen Bedeutung ausgebrochen wird. Dies funktioniert mit allen Metazeichen außerhalb von Zeichenklassen. (Anmerkung: Bei vielen Programmiersprachen und Software-Werkzeugen sind Escapes auch innerhalb von Zeichenklassen zulässig, aber nicht bei egrep. Hier würde der Backslash als normales Zeichen in die Liste der von der Zeichenklasse erlaubten Zeichen aufgenommen.) Wenn vor einem Metazeichen ein Backslash steht, verliert es seine besondere Bedeutung und wird zu einem Literal. Wenn Sie wollen, können Sie die Sequenz »Backslash-Metazeichen« als weitere Metasequenz ansehen, die das ursprüngliche Literal erzeugt. Es funktioniert so oder so.

Ein anderes Beispiel: ˹\([a­zA­Z]+\)˼ kann benutzt werden, um nach einem eingeklammerten Wort zu suchen, etwa ››(sehr)‹‹. Die Backslashes in den Sequenzen ˹\(˼ und ˹\)˼ nehmen den Klammern ihre spezielle Bedeutung, und der reguläre Ausdruck sucht im Text nach literalen runden Klammern.

Vor einem Nicht-Metazeichen kann ein Backslash Verschiedenes bewirken; das hängt vom verwendeten Werkzeug oder sogar von dessen Version ab. Wie wir gesehen haben, behandeln manche Versionen von egrep die Sequenzen ˹\<˼ , ˹\>˼ , ˹\1˼ usw. als Metasequenzen, andere nicht. Wir werden in späteren Kapiteln mehr davon sehen.

  

zum Seitenanfang

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Reguläre Ausdrücke bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:
   

Copyright der deutschen Ausgabe © 2008 by O’Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Reguläre Ausdrücke" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstr. 81, 50670 Köln