Sie sind auf Seite 1von 43

Reguläre Ausdrücke

L. Piepmeyer: Database Systems 1


Beispiel
Eine Textdatei nach Worten durchsuchen, die den Namen
Schmidt, Schmitt oder Schmid gefolgt von einer Zahl
enthalten.

Erste Lösung: Mit Hilfe von Zeichenvergleichen in


Programmiersprachen wie C oder Java.
Nachteile:
• Hoher Aufwand
• Starke Fehleranfälligkeit
• Mangelnde Robustheit
Beispiel

Zweite Lösung:
Das Muster 'Schmidt‚ Schmitt oder Schmid gefolgt von einer
Zahl' mit einer vorgegebenen Syntax beschreiben und dann
ein Werkzeug nutzen, das mit Hilfe dieser Syntax Texte
durchsuchen kann.

In SQL können sehr einfache Muster im Zusammenhang mit


like definiert werden.
Reguläre Ausdrücke

• Mit regulären Ausdrücken können Texte bequem nach


Mustern durchsucht werden.
• Reguläre Ausdrücke (Regexps) folgen einer Syntax.
• Die Nutzung ist ein unverzichtbares Werkzeug für alle
Informatiker.
• Regexps sind in Shells und Programmiersprachen
integriert und können daher vielfältig genutzt werden.

4
Ein Werkzeug

Oft wird das beliebte Werkzeug grep genutzt


grep pattern file
Es gibt verschiedene Varianten der Regexp-Syntax.
In den Beispielen arbeiten wir mit extended regular
expressions (EREs). Hier ist das Werkzeug egrep
geeignet.
Postleitzahlen

Die Beispiele beziehen sich auf die Datei plzBW.tab:

Baden-Württemberg 64754 Eberbach (Baden)


Baden-Württemberg 68159 Jungbusch
Baden-Württemberg 68159 Mannheim, Universitätsstadt
Baden-Württemberg 68161 Schwetzingerstadt
...
Die Felder sind durch Tabulatorzeichen getrennt.
Sehr einfach
Mit den Werkzeugen grep und egrep (e für extended) kann
man effizient nach Textmustern in Texten suchen:

Welche Orte in Baden-Württemberg enthalten ein 'F‘?


egrep "F" plzBW.tab

Welche Orte in Baden-Württemberg enthalten den Text


'Fu'?
egrep "Fu" plzBW.tab
Der Unix-Werkzeugkasten

Wir machen uns das Leben etwas leichter, indem wir in der
Shell eine Funktion defineren:

function plzgrep { egrep "$1" plzBW.tab; }


export -f plzgrep
plzgrep "Fu"

8
Auswahlen

Welche Orte in Baden-Württemberg enthalten ein 'X‘, ein 'Y'


oder ein 'Z‘?
plzgrep "[XYZ]"

Die Auswahl "[XYZ]" ist kein einfacher Text, sondern ein


regulärer Ausdruck. Das Werkzeug egrep findet alle Zeilen
in der Datei, die Teile enthalten, die dem Ausdruck genügen.
Auswahlen

Zeichenintervalle können wie folgt genutzt werden


plzgrep "[X-Z]“

Was sucht der folgende Ausdruck?


plzgrep "[1-38-9]"
Zeichen ausschließen

Der Ausdruck "[^4-6]" liefert alle Teile von Zeilen, die


keines der Zeichen 4, 5 oder 6 enthalten. Das Zeichen ^
dient hier dem Ausschluss.
Reihenfolge und Auswahlen
Die Reihenfolge spielt bei der Auswahl keine Rolle:
plzgrep "[XYxy]"
Passt für Texte, die ein großes oder kleines x oder y
enthalten.

Wenn die Reihenfolge eine Rolle spielt:


plzgrep "[1-3][7-9]"
Alle Postleitzahlen, in denen eine Ziffer zwischen 1 und 3
auftritt, der (unmittelbar) eine Ziffer zwischen 7 und 9 folgt.
Die Option

Es soll zwei 0-en geben, zwischen denen eine Ziffer


zwischen 7 und 9 stehen kann:
plzgrep "0[7-9]?0"
Kardinalitäten
Alle PLZ mit vier aufeinanderfolgenden Zahlen zwischen 7
und 9
"[7-9]{4}"

"7{5}" ist das Gleiche wie 77777

"7{2,5}" die 7 ist mindestens zweimal und maximal


fünfmal aufeinander folgend enthalten.

Bei Postleitzahlen (fünfstellig) ist das Ergebnis das Gleiche


wie "7{2,}"
Kardinalitäten

"7{1,}" liefert das gleiche Ergebnis wie "7+"


"7{0,}" liefert das gleiche Ergebnis wie "7*"

Die Varianten mit + und * sind aber wesentlich weiter


verbreitet.
Zu viele Ergebnisse

Alle Orte, die das Wort 'Baden' enthalten


plzgrep "Baden"
Warum liefert der Ausdruck nicht das gewünschte Ergebnis?
Komplexere Muster

Geliefert wurden alle Zeilen, die das Wort 'Baden‘ enthalten.

Das Muster '.' repräsentiert ein beliebiges Zeichen. Wir


wollen alle Zeilen, die eine Postleitzahl enthalten, die von
beliebig vielen beliebigen Zeichen gefolgt wird und dann
vom Wort 'Baden‘.
plzgrep "[0-9]{5}.*Baden"
Fehler

Warum liefert
plzgrep "?"
(etwa bei macOS) die Fehlermeldung
egrep: repetition-operator operand invalid
Maskiert

Lösung: Symbole, die zur Regexp-Syntax gehören, mit \


maskieren
plzgrep "\?"
Maskiert

Welche Orte enthalten einen Bindestrich? Die Anweisung


plzgrep "\-"
liefert alle Orte , warum?
Maskierung

Die Maskierung nutzt den Perl-Regexp-Dialekt, der teilweise


aktiviert werden muss:
grep -P "\-" plzBW.tab

21
Maskiert

Alle Orte mit Bindestrich


Lösung:
grep -P "[0-9].*\-" plzBW.tab
Sonderzeichen

Mit
cat plzBW.tab | hexdump -C
kann man erkennen, dass die drei Felder Land, PLZ, Ort
jeweils durch Tabulatoren (0x09) getrennt werden.
Das allgemeine Format für jede Zeile kann jetzt auch
präziser mit einer Regexp beschreiben werden:
Baden\-Württemberg\t[0-9]{5}\t.+
Alternativen

Alle Orte, die das Wort 'Baden' oder das Wort 'Württemberg'
enthalten?
[0-9]{5}\t.+(Baden|Württemberg)
Gruppen
Mit den Klammern kann man gruppieren. Gruppen kann
man referenzieren.
Orte, in denen sich ein Paar aufeinanderfolgender
Buchstaben wiederholt.
[0-9]{5}\t.*(.{2})\1

Die Syntax nutzt den Perl-Regexp-Dialekt, der teilweise


aktiviert werden muss:
grep -P "[0-9]{5}\t.*(.{2})\1" plzBW.tab
Einschränkungen
Werden grep und egrep ohne Optionen genutzt, wird die
gesamte Zeile angezeigt, in der ein Text existiert, der der
RegExp genügt. Mit der Option –o wird nur der ‚Match‘
angezeigt
Beispiel:
grep -P "[0-9]{5}\t.*(.{2})\1" plzBW.tab
Baden-Württemberg 89143 Beiningen
grep -oP "[0-9]{5}\t.*(.{2})\1" plzBW.tab
89143 Beinin
HTML
In HTML werden Dokumente wie folgt verlinkt:
<a href="http://www.hfu.de/" target="_blank">Zur HFU</a>

Wie kann man mit regulären Ausdrücken a-tags


beschreiben?
Idee:
<a href=".*".*>
Greedy
<html>
<a href="http://www.hfu.de/" target="_blank">Zur HFU:</a>
</html>

egrep -o '<a href=".*".*>' index.html


Liefert zu viel:
<a href="http://www.hfu.de/" target="_blank">Zur HFU:</a>
Greedy
Das Muster '.*' ist gierig (Greedy) und konsumiert
möglichst viele Zeichen. In unserem Fall ist dies
'target="_blank">Zur HFU:</a'
Es sind ja alle beliebigen Zeichen!
Zwei Lösungen:
1. Nicht mehr jedes beliebige Zeichen zulassen
egrep -o '<a href=".*"[^>]*>' index.html
2. Von Greedy auf Lazy umschalten:
grep -oP '<a href=".*".*?>' index.html
Warum nicht immer Lazy?

• Die Greediness ist vor allem für Anfänger eine


Problemquelle bei der Formulierung regulärer Ausdrücke.
• Laziness als Standardverhalten ist keine befriedigende
Lösung, da die Laufzeiten sich erheblich verschlechtern
können.
HTML-Parser

Tipp:
HTML nur in Ausnahmen mit regulären Ausdrücken parsen.
Hier gibt es spezielle HTML-Parser wie JSoup.
Zeichenklassen

\w repräsentiert ein Zeichen in einem Wort: Buchstaben,


Zahlen oder _. \W repräsentiert ein Zeichen, das nicht in
Worten auftritt
\d und \D analog für Ziffern (digit)
\s und \S analog für Whitespace (space)
\b und \B analog für Wortgrenzen (border). Es wird nur die
Grenze repräsentiert, aber kein Zeichen.
Mit Hilfe der Zeichenklassen kann das Zeilenformat unserer
Datei sehr elegant beschrieben werden:
\bBaden\-Württemberg\b\t\b\d{5}\b\t\b\w+\b.*
Dialekte
Wie in anderen Sprachen, gibt es auch bei regulären
Ausdrücken Dialekte:
Die folgende Anweisung
echo "k47" | grep -o "k[0-9]*"
liefert k47

Will man nur die Zahl haben, hilft das ‚Lookbehind‘ der Perl-
compatible regular expression (PCRE)
echo "k47" | grep -P -o "(?<=k)[0-9]*"
Markierungen
Nützlich ist oft das Symbol für den Zeilenanfang und das
Zeilenende:
^ Zeilenanfang
$ Zeilenende

Wo tritt Baden noch an anderer Stelle als am Zeilenanfang


auf?
plzgrep "[^^]Baden"
Nicht nur in grep

• Reguläre Ausdrücke werden nicht nur von Werkzeugen


wie grep oder egrep genutzt.
• In gängigen Programmiersprachen (und auch in SQL)
sind reguläre Ausdrücke integriert. Beispiel (H2):
select * from posts
where regexp_like(user, 'H.*F.*')
• In Java sind sie in die Standard Java-API integriert.
Die einfachste Variante
In Java muss in Regexps der Backslash immer maskiert
werden.

import java.util.regex.*;

public class Main {


public static void main(String[] args) {
boolean b = Pattern.matches("\\w{5}", "hello");
System.out.println(b);
...
Die einfachste Variante

Der ganze Text muss dem Muster genügen. Der


folgende Java-Ausdruck liefert false!

Pattern.matches("\\w{5}", "regular");
Etwas komplexer
Mit einigen zusätzlichen Objekten arbeitet das folgende
Fragment. Das Ergebnis ist das Gleiche.

Pattern p = Pattern.compile("\\w{5}");
Matcher m = p.matcher("hello");
b = m.matches();
System.out.println(b);

Auf den ersten Blick sieht das Ergebnis sperrig aus.


Den Nutzen sehen wir noch.
Mit Chaining

Werden die Zwischenergebnisse nicht gebraucht, kann


man auch chaining anwenden:

b = Pattern
.compile(".s")
.matcher("as")
.matches();
System.out.println(b);
Treffer anzeigen
Das Objekt vom Typ Matcher kann genutzt werden,
um die Treffer explizit anzuzeigen:

Matcher matcher = Pattern


.compile("\\d{5}")
.matcher("0123456789");
while(matcher.find()){
String match = matcher.group();
int start = matcher.start();
int end = matcher.end();
System.out.println(match+", start: "+start+", end: "+end);
}
Treffer anzeigen
Es ergibt sich

01234, start: 0, end: 5


56789, start: 5, end: 10

Das Ergebnis zeigt auch, wie die Regexp-Engine arbeitet.


Literatur

Die
Referenz

Einige Beispiele aus den Folien orientieren sich teilweise an


https://danielfett.de/de/tutorials/tutorial-regulare-ausdrucke/

Das könnte Ihnen auch gefallen