Reguläre Ausdrücke

Werbung
Reguläre Ausdrücke
Name
1
AnPr
Klasse
Datum
Allgemeines
Reguläre Ausdrücke (engl. Regular Expressions) dienen dazu, Texte bzw. Textpassagen zu beschreiben,
indem Texte als Mengen von Zeichen interpretiert werden und die Beschreibung einem theoretischen Ansatz
auf Basis der Mengenlehre folgt. Aufgrund der Fähigkeit zu beschreiben folgen auch die wichtigsten Anwendungsgebiete von Regulären Ausdrücken, dem Filtern von Texten, dem Extrahieren und dem Ersetzen
von Textfragmenten.
Speziell unter UNIX werden Reguläre Ausdrücke gerne für Verarbeitungsskripte verwendet, da hier sehr
einfach auf die Ausgaben von Batchprogrammen zugegriffen werden kann und mit Hilfe des Programms
„grep“ (global/regular expression/print) die gewünschten Inhalte gesucht werden können.
Die Anwendung von Regulären Ausdrücken auf Texte erfolgt durch entsprechende Implementierungen, welche (leider) unterschiedliche Funktionsumfänge und Dialekte hervorgebracht haben. Am verbreitetsten ist
PCRE (Perl Compatible Regular Expressions), welches sich an der Perl Implementierung orientiert. Weiterhin wird bei den Regulären Ausdrücken zwischen den grundlegenden (basic) und den erweiterten (extended)
Regulären Ausdrücken unterschieden, wobei die Erweiterungen die ursprüngliche theoretische Basis der
Mengenlehre sprengen. Als Beispiel sei hier die Rückwärtsreferenzen (backtracking) genannt, welche es
ermöglichen auf bereits gefundene Textfragmente wiederum Reguläre Ausdrücke anzuwenden.
Das hier vorliegende Dokument dient dazu, die wesentlichen Elemente von Regulären Ausdrücke verstehen
zu lernen und eine Java Implementierung zur Anwendung und zum Üben von Regulären Ausdrücken zu
realisieren.
2
Java Implementierung
Java bietet in der Bibliothek javax.util.regex.* (siehe:
http://docs.oracle.com/javase/7/docs/api/java/util/regex/package-summary.html bzw.
http://java.sun.com/ docs/ books/ tutorial/essential/regex/index.html) die notwendigen Klassen und Methoden an, um Reguläre Ausdrücke anwenden zu können. Hierbei gibt es zwei wesentliche Klassen:
Pattern: Dies ist eine Klasse, welche den Regulären Ausdruck kompiliert und somit mehrfach anwendbar
macht, wobei die Anwendung durch den Machter durchgeführt wird. Alternativ kann ein Pattern auch direkt
den Regulären Ausdruck anwenden, wobei dies bei mehrfacher Ausführung eine ungünstiger Performance
mit sich bringt, da der Ausdruck bei jedem Match (Treffer) neu kompiliert wird.
Matcher: Der Machter ist in der Lage ein kompiliertes Pattern anzuwenden und verschiedene komfortable
Methoden (z.B.: Suchen, Ersetzen) abhängig von den Matches durchzuführen.
Was: Anwendung von Regulären Ausdrücken
Programmiersprache: Java
Pattern p1 = Pattern.compile("u", Pattern.CASE_INSENSITIVE);
Matcher m1 = p1.matcher("Berufsschule");
String s1 = m1.replaceAll("a");
Pattern p2 = Pattern.compile("[0-9][^0-9]*");
Matcher m2 = p2.matcher("8tung, ein Text");
boolean b2 = m2.matches();
Pattern p3 = Pattern.compile("\b\S*a\S*\b");
Matcher m3 = p3.matcher("Peter Paul Mary");
while(m3.find())
{
System.out.println(m3.group());
}
Erklärung:
Pattern p1
Deklaration eines Pattern Objektes
Pattern.compile(
Compillierung der Expression und Erzeugung eines Patterns, wobei als
"u", Pattern.
zweiter Parameter die Flags angegeben sind. Diese werden als Bitmuster
CASE_INSENSITIVE);
in einer int Variable codiert.
ANPR_01_RegexInJava_v03.docx
Seite 1
Reguläre Ausdrücke
AnPr
Was: Anwendung von Regulären Ausdrücken
Programmiersprache: Java
Matcher m1
Deklaration eines Matcher Objektes
p1.matcher(
Anwendung der Expression auf den Text und Erzeugung eines Matcher
"Berufsschule");
Objektes.
m1.replaceAll("a");
Ersetzen jeder Fundstelle mit dem angegebenen String (hier „a“).
m2.matches();
Prüfung, ob der Ausdruck exakt matcht
m3.find()
Prüfung, ob das Pattern im Text gefunden wurde, incl. Positionierung
eines Zeigers auf diese Fundstelle.
m2.group();
Ausgabe der gefundenen Stelle, auf dem der aktuelle Zeiger steht.
Programmierhinweis:
Nutzung eines Matchers
Aufgrund der komfortablen Methoden ist es prinzipiell sinnvoll einen Matcher zu verwenden. Bei einmaliger Prüfung, ob der Text der Expression
entspricht kann jedoch auch folgende Methode verwendet werden:
Pattern.matches("0-9][^0-9]*",
"8tung, ein Text");
Wichtig ist jedoch, dass Matcher nicht Threadsicher (thread save) sind.
Die wichtigsten Methoden vom Matcher sind:
matches(): Liefert true zurück, wenn der gesamte untersuchte Text dem Regulären Ausdruck entspricht.
find(): Liefert true zurück, wenn innerhalb des Textes eine Passage gefunden wurde, welche dem Regulären Ausdruck entspricht. Die Suche beginnt beim ersten Aufruf am Anfang des Textes und endet mit dem
letzten Zeichen, welches dem Regulären Ausdruck entspricht. Sofern reset() nicht aufgerufen wurde,
sucht die Methode beim zweiten Aufruf beim nächsten Zeichen nach dem vorausgegangenen Match weiter,
es können somit sequenziell alle Matches abgearbeitet werden.
group(): Liefert, sofern nach find() aufgerufen, die Textpassage zurück, welche mit dem vorausgegangenen find() gefunden wurde.
start(): Liefert, sofern nach find() aufgerufen, die Position des ersten Zeichens der Textpassage zurück, welche mit dem vorausgegangenen find() gefunden wurde.
stop(): Liefert, sofern nach find() aufgerufen, die Position des letzten Zeichens der Textpassage zurück, welche mit dem vorausgegangenen find() gefunden wurde.
reset(): Setzt den Zeiger der letzten durch find() gefundenen Textpassage wieder auf die Position 0
zurück.
Die Flags, welche für die Kompilierung des Patterns akzeptiert werden sind wie folgt:
Kanonische Äquivalenz – also wenn zwei UNICODE Zeichen zur selben Darstellung führen, werden sie als identisch angesehen.
CASE_INSENSITIVE: Großbuchstabe und Kleinbuchstabe werden als identisch angesehen.
COMMENTS:
Kommentare (Zeichen # bis Zeilenende) innerhalb des Regulären Ausdrucks werden ignoriert.
DOTALL:
Der Punktoperator berücksichtigt auch das Zeilentrennzeichen.
LITERAL:
Metazeichen werden nicht als solches berücksichtigt, sondern werden literal interpretiert.
MULTILINE:
Die Operatoren ^ und $ gelten für den Anfang und das Ende jeder Zeile, nicht des
gesamten Textes.
UNICODE_CASE:
Bei CASE_INSENSITIVE wird der Vergleich über den UNICODE Standard
durchgeführt.
UNIX_LINES:
Als Zeilentrenner wird lediglich \n interpretiert.
CANON_EQ:
Bei mehreren zu setzenden Flags, müssen diese bitweise mit dem „Oder Operator“ gesetzt und anschließend
übergeben werden.
Darüber hinaus bietet Java auch in der String Klasse Methoden zur Prüfung des Strings, wie z.B. matches(strRegex), replaceAll(strRegex, strReplacement) an. Dies ist für einfache, einmalige Aufgaben gedacht.
Details unter http://java.sun.com/javase/6/docs/api/java/lang/String.html.
Seite 2
AnPr
3
Reguläre Ausdrücke
Syntax von Regulären Ausdrücken
Grundsätzlich hilft bei Regulären Ausdrücken das Netz weiter – es gibt hunderte von Seiten, die einem weiterhelfen. Eine sehr gut gestaltete Seite ist: http://www.regular-expressions.info/
Hier finden sich neben dem hier dargelegten noch viele weitere nützliche Tipps. Zum schnellen validieren
kann man sich auf http://www.regexplanet.com umsehen.
3.1 Identifikation von Zeichen
Die einfachste Form von Regulären Ausdrücken ist die Identifikation von einzelnen Zeichen. Hierbei wird
der zu suchende Buchstabe einfach als Regulärer Ausdruck interpretiert. Beachtung von Groß/Kleinschreibung wird in der Regel als Parameter in der RegEx Implementierung gesetzt. Bei Java ist dies
das Flag CASE_INSENSITIVE.
RegEx:
u
Ersetzung:
a
Text:
Berufsschule
Führt bei Ersetzung zu:
Berafsschale
Genauso können mehrere Zeichen als Kette gesucht werden:
RegEx:
ul
Ersetzung:
a
Text:
Berufsschule
Führt bei Ersetzung zu:
Berufsschae
Zu beachten ist, dass Metazeichen mit „\“ zu escapen sind:
[]\^$.|?*+()
Achtung: Im Java String muss das Escapezeichen ebenfalls escaped werden. Für das Escapen von [
darf somit nicht \[ , sondern muss mit \\[ gesetzt werden.
Weiterhin sind noch besondere Zeichen definiert worden, wobei diese z.T. abhängig von der Implementierung sind. Die hier angegebenen Zeichen entstammen der Klassenbeschreibung von java.util.regex.Pattern:
Sonderzeichen:
\\
\0n
\0nn
\0mnn
\xhh
\uhhhh
\t
\n
\r
\f
\a
\e
\cx
Bedeutung
Das backslash Zeichen
Das Zeichen mit dem Oktalwert 0n (0 <= n <= 7)
Das Zeichen mit dem Oktalwert 0nn (0 <= n <= 7)
Das Zeichen mit dem Oktalwert 0mnn (0 <= m <= 3, 0 <= n <= 7)
Das Zeichen mit dem Hexadezimalwert 0xhh (0 <= h <= F)
Das Zeichen mit dem Hexadezimalwert 0xhhhh (0 <= h <= F) als Unicode
Das Tabulatorzeichen ('\u0009')
Das newline (line feed) Zeichen ('\u000A')
Das carriage-return Zeichen ('\u000D')
Das form-feed Zeichen ('\u000C')
Das Alarm (bell) Zeichen ('\u0007')
Das escape Zeichen ('\u001B')
Das Kontroll-Zeichen zu x (bspw. ctrl a: „\ca“)
Zusatzinfo Zeilenterminierung:
Zeilenterminierungen (bzw. Lineterminators) haben eine besondere Bedeutung, da sie zum einen üblicherweise nicht ausgegeben werden und zum anderen eine unterschiedliche Interpretation bei UNIX und
Windows existiert. Darüber hinaus besteht unter Windows die Zeilenterminierung aus zwei Zeichen.
Seite 3
Reguläre Ausdrücke
AnPr
Hier die relevanten Zeilenumbruch - Zeichen:
Terminator:
\n
\r\n
\r
\u0085
\u2028
\u2029
Bedeutung:
Newline (bzw. Line feed) Zeichen (UNIX)
Carriage Return Zeichen gefolgt von einem Newline Zeichen (Windows)
Einzelnes Carriage Return Zeichen
Next Line Zeichen
Line Separator Zeichen
Absatz Separator Zeichen
Weiterhin sind noch POSIX Zeichenklassen für den US – ASCII Code und eigene Java Identifikationen definiert worden. Diese können in der Klassenbeschreibung von java.util.regex.Pattern ersehen werden
(http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html).
3.2 Identifikation von Zeichengruppen
Es ist möglich, verschiedene Zeichen als bestimmte Zeichengruppen anzugeben. Hierzu gibt es verschiedene
Möglichkeiten. Die einfachste Möglichkeit ist die Zusammenfassung in Eckigen Klammern [ ]. Innerhalb
dieser können wiederum verschiedene Ausdrücke stehen:
Einzelne Zeichen werden auch einzeln betrachtet, insofern gibt [aeiou] an, dass nach allen a, e, i, o bzw. u
gesucht wird.
RegEx:
[aeiou]
Ersetzung:
+
Text:
Berufsschule
Führt bei Ersetzung zu:
B+r+fssch+l+
Zusammenhängende Zeichengruppen können mit Bindestrich angegeben werden. [a-f] sucht somit alle
Zeichen von a bis f, wobei die ASCII Codetabelle für die Bestimmung der Bereiche herangezogen wird.
RegEx:
[a-f]
Ersetzung:
+
Text:
Berufsschule
Führt bei Ersetzung zu:
B+ru+ss+hul+
Durch Aneinanderreihung von einzelnen Zeichen bzw. zusammenhängenden Zeichengruppen können diese
auch kombiniert werden. [aeiou][a-f] sucht somit nach Stellen, an denen a, e, i, o bzw. u vor einem a, b, c, d,
e oder f stehen.
RegEx:
[aeiou][a-f]
Ersetzung:
+
Text:
Berufsschule
Führt bei Ersetzung zu:
Ber+sschule
Die Zeichengruppen können auch negiert werden, indem ein Zirkumflex (^) Zeichen nach der öffnenden
Klammer platziert wird. [^a-f] sucht somit nach allen Zeichen, außer a, b, c, d, e oder f.
RegEx:
[^a-f]
Ersetzung:
+
Text:
Berufsschule
Führt bei Ersetzung zu:
+e++f++c+++e
Weiterhin gibt es vordefinierte Zeichengruppen, welche mit Ausnahme des Punktes mit Hilfe von Zeichengruppen selbst formuliert werden können.
Zeichengruppe:
.
\d
\D
\s
\S
\w
1
Bedeutung:
Jedes beliebige Zeichen1
Eine beliebige Ziffer
Alles außer Ziffern
Ein „Whitespace“ Zeichen
Alles außer Whitespace Zeichen
Ein beliebiges „Wort Zeichen“ (incl. Unterstrich, ohne Umlaute)
Ersatzausdruck:
nicht sinnvoll
[0-9]
[^0-9]
[\t\n\x0B\f\r]
[^\t\n\x0B\f\r]
[a-zA-Z_0-9]
Je nach Setting der Implementierung wird der Line Terminator ignoriert. Bei Java Pattern wird dies durch
das setzen des Flags DOTALL erreicht.
Seite 4
AnPr
Reguläre Ausdrücke
Zeichengruppe:
\W
Bedeutung:
Alles außer “Wort Zeichen”
Ersatzausdruck:
[^a-zA-Z_0-9]
Implizit wurde bereits auf die Union von zwei Gruppen hingewiesen ([aeiou][a-f]). Darüber hinaus existiert
noch die Intersection („&&“), welche nur die Zeichen identifiziert, welche in beiden Ausdrücken vorhanden
ist. Bspw. sucht [a-z&&[^aeiou]] alle Zeichen des Alphabets außer a, e, I, o bzw. u.
Eine weitere Operation stellt die Alternative (|) dar, in der zwei Optionen angegeben werden (cats|dogs)
würde beim Satz „It is raining cats and dogs“ zwei Matches haben. Diese beiden Funktionen existieren jedoch nur in wenigen Implementationen – Java ist eine davon:
(http://docs.oracle.com/javase/tutorial/essential/regex/char_classes.html)
3.3 Quantoren
Um zu spezifizieren, wie oft gesuchte Sequenzen vorkommen dürfen, werden Quantoren benötigt. Sie werden an das gesuchte Zeichen oder Zeichengruppen angehängt (bswp. sucht a{3} alle Vorkommnisse von
aaa).
Quantor:
?
+
*
{n}
{min,}
{,max}
{min,max}
Bedeutung:
Voranstehender Ausdruck muss 0 oder einmal vorkommen.
Voranstehender Ausdruck muss 1 oder mehrmal vorkommen.
Voranstehender Ausdruck muss 0, 1 oder mehrmal vorkommen.
Voranstehender Ausdruck muss exakt n mal vorkommen.
Voranstehender Ausdruck muss mindestens min mal vorkommen.
Voranstehender Ausdruck darf maximal max mal vorkommen.
Voranstehender Ausdruck muss mindestens min mal und maximal max mal vorkommen.
Wichtig zu beachten ist, dass sich die Quantoren auf den vorausgegangen Ausdruck beziehen, nicht auf die
gefundene Sequenz. So findet [0-9]{3} sowohl die 000, als auch die 007 im Text „von 000 bis 007 ist ein
weiter Weg“.
3.3.1 Gieriges Verhalten (greedy)
Grundsätzlich verhalten sich die Quantoren „gierig“ (engl. greedy). Dies bedeutet, dass sämtliche möglichen
Matches gefunden (und bspw. bei Ersetzung auch angewendet) werden. Folgendes Beispiel soll dies verdeutlichen:
RegEx:
s+
Ersetzung:
+
Text:
Berufsschule
Führt bei Ersetzung zu:
Beruf+chule
Der Ausdruck sucht somit ein, oder mehrere „s“, interpretiert sie als einen Treffer und ersetzt sie mit einem
„+“. Der Match hat somit den gesamten Bereich als „seinen Treffer“ aufgefasst. Anschließend fängt er mit
einer neuen Suche an. Gierig bedeutet also, dass der letzte mögliche Match verwendet wird.
3.3.2 Genügsames Verhalten (reluctant)
Nun kann man in den meisten Implementierungen von Regulären Ausdrücken (den Perl Vorgaben folgend)
die Quantoren auch „genügsam“ (engl. reluctant, oder non-greedy) auszuführen. Hierfür wird im Regelfall
ein „?“ nachgestellt:
RegEx:
s+?
Ersetzung:
+
Text:
Berufsschule
Führt bei Ersetzung zu:
Beruf++chule
Man sieht am Ergebnis, dass Java pro Wort zwei Matches gefunden hat. Der erste begnügt sich also mit dem
ersten gefundenen Buchstaben und beginnt anschließend wieder erneut mit einer Suche. Genügsam bedeutet
also, dass der erste mögliche Match verwendet wird.
3.3.3 Possesives Verhalten (possessive)
Mit dem possesiven Verhalten ist es möglich, trotz gierigem Verhalten, den gesamten Match auf eine zusammenhängende Gruppe zu reservieren. Hierzu muss ein „+“ nachgestellt werden. Um das Verhalten zu
verdeutlichen, werden hier der normale gierige und der possesive Ausdruck dargestellt:
Seite 5
Reguläre Ausdrücke
RegEx:
.
.+
AnPr
Ersetzung:
+
+
Text:
Bs7
Bs7
Führt bei Ersetzung zu:
+++
+
Der sinnvollste Anwendungsfall für possesives Verhalten ist weniger die Ersetzung von Text, als die Filterung. Eine Prüfung auf ein Matching der oberen Zeile würde false, auf die untere true ergeben. Possesives
Verhalten ist in nur wenigen Implementierungen realisiert, Java ist eine davon.
3.4 Grenzen
Oftmals ist es notwendig, Zeichenketten anhand ihrer Position in Zeilen oder Wörtern zu identifizieren. Zum
Beispiel erwirkt der Operator \b, dass die Zeichenkette an der Grenze eines Wortes stehen muss:
RegEx:
\ber
er\b
\ber\b
Ersetzung:
+
+
+
Text:
er, der Herr erfährt mehr.
er, der Herr erfährt mehr.
er, der Herr erfährt mehr.
Führt bei Ersetzung zu:
+, der Herr +fährt mehr.
+, d+ Herr erfährt mehr
+, der Herr erfährt mehr.
Folgende Grenzen kennt Java:
Grenze:
^
$
\b
\B
\A
\G
\Z
\z
Bedeutung:
Anfang einer Zeile (Zeichen nicht mit dem Negierer verwechseln – dieser steht nicht am
Anfang)2
Ende einer Zeile2
Wortgrenze
Nichtwortgrenze (also das Pattern darf nicht an einer Wortgrenze stehen)
Anfang des untersuchten Strings
Ende des vorausgegangenen Matches
Ende des untersuchten Strings, ohne dem finalen Terminator, sofern er existiert
Ende des untersuchten Strings
3.5 Nummerierung und Capturing Groups
Eine fortgeschrittene Möglichkeit mit Regulären Ausdrücken zu hantieren sind die Capturing Groups, welche beim sogenannten „backreferencing“ genutzt werden. Zuerst ist es wichtig zu verstehen, dass Ausdrücke
in Klammern durchnummeriert sind. Beispielsweise hat der Ausdruck ((A)(B(C))) vier Gruppen:
1. ((A)(B(C)))
2. (A)
3. (B(C))
4. (C)
Die Nummerierung folgt den öffnenden Klammern. Wenn nun Ausdrücke verwendet werden, so kann mittels einer Rückwärtsreferenz („backreference“) diese wieder mittels \n referenziert werden, wobei n die
Nummer des referenzierten Ausdrucks ist. Beispielsweise sucht der Ausdruck (\d\d)\1 zwei aufeinanderfolgende Ziffern, wobei diese zweimal hintereinander stehen müssen (dies wird durch die Referenz \1 gefordert,
also suche zwei beliebige Ziffern hintereinander „\d\d“ und dann das gleiche nochmal „\1“):
RegEx:
(\d\d)\1
(\d\d)\1
Ersetzung:
+
+
Text:
33121233
33122133
Führt bei Ersetzung zu:
33+33
33122133
In der unteren Zeile wird das Pattern nicht wiedergefunden, da es kein Ziffernpärchen gibt, welches zweimal
hintereinander vorkommt.
2
Multiline Identification muss im Regelfall in der Implementierung aktiviert werden
Seite 6
AnPr
Reguläre Ausdrücke
3.6 Look around assertions
Es ist möglich Ausdrücke entsprechend ihrer Position eines zweiten Ausdruckes zu bestimmen. Wenn z.B.
ein Buchstabe „u“ nur dann gefunden werden soll, wenn er vor einem „f“ steht, kann dies mit einem entsprechenden Ausdruck realisiert werden. Grundsätzlich kann der vorausgehende Ausdruck (look ahead) oder der
nachstehende Ausdruck (look back) gesucht werden.
3.6.1 Look ahead assertion
Bei einer vorausschauenden Prüfung wird mit (?=X) geprüft, ob der gesuchte Ausdruck vor einem Referenzausdruck X steht (positive look ahead assertion). Der Ausdruck (?!X) negiert dies und prüft, ob der gesuchte Ausdruck nicht vor einem Referenzausdruck X steht.
RegEx:
u(?=f)
u(?!f)
Ersetzung:
+
+
Text:
Berufsschule
Berufsschule
Führt bei Ersetzung zu:
Ber+fsschule
Berufssch+le
3.6.2 Look back assertion
Die zurückschauende Prüfung wird mit (?<=X) formuliert und prüft, ob der gesuchte Ausdruck nach einem
Referenzausdruck X steht (positive look behind assertion). Der Ausdruck (?<!X) negiert dies und prüft, ob
der gesuchte Ausdruck nicht nach einem Referenzausdruck X steht.
RegEx:
(?<=r)u
(?<!r)u
Ersetzung:
+
+
Text:
Berufsschule
Berufsschule
Führt bei Ersetzung zu:
Ber+fsschule
Berufssch+le
3.7 Sonderfall Zeilenumbruch
Wie oben bereits angedeutet, hat der Punkt Operator „.“ nicht zwingend die Eigenschaft, den Zeilenumbruch
als eigenes Zeichen zu interpretieren, dies muss je nach Implementierung aktiviert werden. Wenn z.B. der
Text zwischen zwei Klammern identifiziert werden muss, so kann folgender Ausdruck ein Problem darstellen:
\(.*\)
Wenn die öffnende Klammer in Zeile1 und die schließende Klammer in Zeile 2 steht, würde bei Nichtinterpretation des Zeilenumbruches kein Match gefunden werden, da zwischen den beiden Klammern ja nicht
eine beliebige Anzahl von Zeichen stehen würde sondern zwei voneinander getrennte Gruppen von Zeichen.
Seite 7
Reguläre Ausdrücke
4
AnPr
Lizenz
Diese(s) Werk bzw. Inhalt von Maik Aicher (www.codeconcert.de) steht unter einer
Creative Commons Namensnennung - Nicht-kommerziell - Weitergabe unter gleichen Bedingungen 3.0 Unported Lizenz.
Seite 8
Herunterladen