In ETL-Prozessen, die sich mit manuell eingegebenen Daten wie Kundendaten oder ähnlichem beschäftigen gibt es fast zwangsläufig irgendwann die Situation, Daten aus einem Textfeld extrahieren zu müssen. Und obwohl das eine fast alltägliche Aufgabe ist, stößt man in real existierenden Projekten immer wieder auf enorm umfangreiche Substring-Methoden zur Erkennung bestimmter Muster. Dabei hat so gut wie jede Programmiersprache dafür ein Konstrukt parat, von dem zwar schon einmal gehört haben dürfte. Leider wird es allerdings leider oft wegen seines vermeintlich komplizierten Syntax oder weil es gerne als „überdimensioniert“ betrachtet wird, ignoriert wird: die regulären Ausdrücke (Regular Expressions oder auch RegEx).
Warum eigentlich „regulär“?
Schon der Name der „regulären“ Ausdrücke mag abschreckend wirken. Manch einer mag sich fragen, was an so einem Ausdruck denn Regulär sein kann, dabei handelt es sich hier um ein relativ einfaches mathematisches Konzept. Dieses Konzept wurde vom amerikanischen Mathematiker Stephen Cole Kleene entwickelt.
Abbildung 1: Stephen Cole Kleene, Quelle: Mathematisches Forschungsinstitut Oberwolfach, http://owpdb.mfo.de/detail?photo_id=2122, Konrad Jacobs, Erlangen
Er verwendete es, um zu formalisieren, wie die Neuronen eines Organismus oder auch informationstheoretische Automaten (http://de.wikipedia.org/wiki/Automat_(Informatik)) auf Stimulation beziehungsweise Input reagieren (Kleene 1956). Die „reguläre Menge“ oder auch „reguläre Sprache“ bezeichnet eine Sprache, die bestimmten formalen Kriterien genügt (http://de.wikipedia.org/wiki/Regul%C3%A4re_Sprache). Die Kenntnis dieser Kriterien ist hilfreich für das Verständnis, wie reguläre Ausdrücke ausgewertet werden und wie sie formuliert werden sollten, um möglichst effiziente Auswertung zu erlauben. Für eine erste Einführung in RegEx, wie wir sie hier bieten wollen, können wir sie allerdings getrost außen vor lassen. Stattdessen können wir hier vereinfacht sagen: Regular Expressions sind eine Sprache, um Mengen von Zeichenketten zu beschreiben.
Warum ist das hilfreich?
Oder konkret am Beispiel gesagt: wenn wir in einem Text das Auftreten der Kombination „Vokal gefolgt von s“ suchen möchten, können wir nacheinander nach „as“, „es“, „is“, „os“ und „us“ suchen. Oder wir finden eine Möglichkeit, dem Computer die Kombination „Vokal gefolgt von s“ verständlich zu machen. Dieses einfache Beispiel sagt noch nicht viel über den Nutzen, den eine derartige Beschreibung hätte. Erweitern wir die Suche aber auf „Vokal gefolgt von einem Konsonanten“, dann hätten wir beim vorherigen Vorgehen fünf mal 21, also 105 Suchen durchzuführen. Der Nutzen einer beschreibenden Sprache wäre jedem, der dieses Vorgehen von Hand ausprobiert, spätestens bei „ag“ klar.
„Kenn‘ ich nicht, brauch‘ ich nicht“?
Das Konzept hinter RegEx ist abstrakt, die Problemstellungen hören sich kompliziert an, doch in Wirklichkeit sind RegEx jedem von uns im täglichen Umgang mit dem PC geläufig. Reguläre Ausdrücke verstecken sich hinter der „Suchen und Ersetzen“-Maske nahezu jedes Texteditors. Im SQL Server Management Studio kann beim „Suchen und Ersetzen“ sogar explizit die RegEx-Syntax aktiviert werden (S. Abbildung 2).
Abbildung 2: RegEx für Suchen und Ersetzen im SSMS
Doch wie lautet die Syntax, mit deren Hilfe RegEx das Suchen von Zeichenketten in Text ermöglicht? Wir möchten im nächsten Abschnitt die wichtigsten Konstrukte von regulären Ausdrücken beschreiben ohne jeden Anspruch auf Vollständigkeit, die hier sicher den Rahmen sprengen würde. Eine gute Übersicht zur Einführung hat Microsoft im MSDN hinterlegt: http://msdn.microsoft.com/en-us/library/az24scfc.aspx.
„Will ich haben“
RegEx stehen wie bereits erwähnt in nahezu jeder Programmiersprache zur Verfügung. Die einzige Hürde vor Verwendung von RegEx besteht darin die Syntax zu lernen, die wir hier kurz und möglichst schmerzlos einführen wollen. Wir werden uns dabei auf die Konstrukte konzentrieren, die nach unserer Erfahrung in der Praxis am hilfreichsten sind.
Zeichenklassen und -gruppen
Zunächst wollen wir uns um die gängigsten Zeichenklassen kümmern. Die Syntax von regulären Ausdrücken klassifiziert Zeichen in verschiedenen Klassen:
Regulärer Ausdruck | Beschreibung | Trifft |
\w | „Word-Character“, das heißt alles außer Satz- und Sonderzeichen, auch Umlaute | „1“, „ä“, „a“, „3“ und „t“ in „1äa3 t-„ |
\d | „Digit“, also Ziffern | „1“ und „3“ in „1äa3 t-„ |
\D | „No digit“ also alles außer Ziffern | „ä“, „a“, “ „, „t“ und „-“ in „1äa3 t-„ |
\s | „Whitespace“ also Tabs, Leerzeichen und Zeilenumbrüche | “ “ in „1äa3 t-„ |
\A, \Z, \b | Anfang (\A), Ende (\Z) der Zeichenkette oder Wortende (\b), d.h. Übergang zwischen \w und nicht \w | |
. | Jedes Zeichen einschließlich Sonderzeichen | „1“, „ä“, „a“, „3“, “ „, „t“ und „-“ in „1äa3 t-„ |
[abc] | Jedes der Zeichen „a“, „b“ oder „c“ | „a“ in „1äa3 t-„ |
[a-c] | Jedes Zeichen von „a“ bis „c“ | „a“ in „1äa3 t-„ |
Gruppierung
In RegEx ist es natürlich auch möglich, Häufigkeiten anzugeben. Ebenso können Zeichengruppen gebildet werden, die gemeinsam auftreten müssen. Diese Gruppen können auch für den einfacheren Zugriff darauf benannt werden.
Regulärer Ausdruck | Beschreibung | Trifft |
a{2} | Zweifaches Auftreten von „a“ direkt hintereinander | „aa“ in „abcdefgaabbcc“ |
a{2,} | Zwei oder mehr „a“ direkt hintereinander | „aa“ und „aaa“ in „abaababbaaa“ |
(ab) | Das Auftreten von „ab“ direkt hintereinander | Zwei Mal „ab“ in „abaabccdAb“, da RegEx Case-Sensitiv sind, ist „Ab“ kein Treffer. |
(?<Treffer>[a-c]{2}) | Das Auftreten von 2 Zeichen von „a“ bis „c“ direkt hintereinander, die Gruppe wird beim späteren Zugriff „Treffer“ genannt. | „ab“, „aa“ und „bc“ in „abaabccdAb“. |
Assertions
Als Zusätzliche Herausforderung gibt es oft Bedingungen der Form „Das gesuchte Wort muss in Klammern stehen“. Für solche Fälle gibt es die so genannten Assertions, auch „Lookaround“ genannt.
Dabei unterscheidet man zwischen zwei Untergruppen, den „Lookaheads“, die garantieren, ob eine bestimmte Gruppe direkt vor der gesuchten Zeichenkette auftritt und den „Lookbehinds“, die garantieren ob eine bestimmte Gruppe direkt nach der gesuchten Zeichenkette auftritt. Beide gibt es in positiven wie auch in negativen Ausprägungen.
Regulärer Ausdruck | Beschreibung | Trifft |
(?<=\[)\w*\b | Positiver Lookbehind, es werden nur Wörter (\w*) gematcht, die direkt auf eine Klammer folgen. Die Expression für negativen Lookbehind ist „(?<!“ | „Klammer“ in „Das ist (eine [Klammerbemerkung])“ |
\b(?!un)\w*\b | Negativer Lookahead, trifft alle Wörter, die nicht mit „un“ beginnen. Die Expression für positiven Lookahead ist „(?=“ | „eindeutig“ und „Erinnerung“ in „unsicher, unklar, eindeutig, Erinnerung“. |
Die Assertions sind auf den ersten Blick vielleicht etwas verwirrend, deshalb wollen wir uns im Folgenden noch ein Praxisbeispiel ansehen.
Zu guter Letzt ein Praxisbeispiel
Wir wollen uns nun einem kleinen Praxisbeispiel widmen. Dafür wollen wir Informationen aus einem Freitextfeld extrahieren. Wir wissen nichts über die Beschaffenheit des Feldinhalts, wir wissen nur, dass zwischen frei eingegebenem Text gelegentlich technische Kennzeichen auftreten. Diese stehen immer in eckigen Klammern. Das Ganze könnte wie folgt aussehen:
Das ist ein Beispieltext bei dem einige [Wörter] in [eckigen Klammern] stehen.
Manchmal treten sie als [Key: Value]-Paare auf.
Beginnen wir also, zunächst mit einer Expression, die den Inhalt von eckigen Klammern trifft. Um zu garantieren dass vor und nach dem gefundenen Wort eckige Klammern stehen, verwenden wir positive Lookahead und Lookbehind-Ausdrücke:
(?<=\[)(\w*)(?=\]).
Testen wir nun mit unserem Beispieltext, erhalten wir als einzigen Treffer das Wort „Wörter“. Das liegt daran, dass \w* automatisch bei Auftreten eines Leerzeichens endet. Folgt also keine schließende Klammer direkt auf das erste Wort in der Klammer, so können wir nicht treffen. Wir können die Expression aber so modifizieren, dass auch mehrere durch Leerzeichen getrennte Wörter in den Klammern stehen können:
(?<=\[)(\w*\ *)*(?=\]).
Mit dieser Expression treffen wir in unserem Beispieltext sowohl „Wörter“ als auch „eckigen Klammern“. Für Key-Value-Paare haben wir noch keine Treffer. Hier möchten wir die Expression gerne so schreiben, dass wir Key und Value direkt voneinander unterscheiden können. Dafür verwenden wir benannte Gruppen. Die Expression lautet dann wie folgt:
(?<=\[)(?<key>(?:\w*\ *)*:? ?)(?<value>\w*)(?=\])
Das Resultat der Anwendung dieses regulären Ausdrucks auf unseren Teststring ist, dass wir 3 Treffer haben. Im ersten enthält die Gruppe „key“ den Wert „Wörter“, die Gruppe „value“ ist leer. Im zweiten Treffer enthält „key“ den Wert „eckigen Klammern“, „value“ ist wieder leer und beim dritten Treffer enthält die Gruppe „key“ den Wert „Key:“ und die Gruppe „value“ den Wert „Value“. Wir haben also nicht nur mit 2 Zeilen C#-Code (eine Zeile zum Ausführen der RegEx und eine zum Loopen der Ergebnisse) den Eingabestring auf Klammerausdrücke untersucht sondern außerdem direkt im Key/Value-Fall die Werte für Key und Value separiert.
Fazit
Reguläre Ausdrücke sind eine sehr komfortable Art, um Werte aus Texten zu extrahieren. Sie können im konkreten Fall fehlertolerant und effizient verwendet werden, um die Texte nicht nur syntaktisch zu bewerten, sie können durch benannte Gruppen den einzelnen Teilpatterns auch eine Bedeutung geben. Dabei verwenden sie eine standardisierte, einheitliche Syntax, die gut dokumentiert ist. Im Sinne der Effizienz und Wartbarkeit sind sie in Projekten deshalb einem Substring-Parsing das in der Regel mehr implizite, versteckte Annahmen verwendet, auf jeden Fall vorzuziehen.
Quellen und Ausblick
Die Anzahl der Dokumente, die sich mit RegEx befassen ist schier unendlich. Für den Einstieg im Microsoft BI-Umfeld, empfiehlt sich die Einführung im MSDN: http://msdn.microsoft.com/en-us/library/az24scfc.aspx
Außerdem hat Microsoft eine Zusammenstellung von Tipps für effiziente RegEx hinterlegt. Ihre Lektüre lohnt sich auf jeden Fall, auch um einen kleinen Einblick in die Funktionsweise der RegEx-Engine von .NET-Sprachen zu erhalten: http://msdn.microsoft.com/en-us/library/gg578045.aspx
Als sehr komfortable Testumgebung kann der RegEx-Tester von Regexhero verwendet werden: http://regexhero.net/tester/
In einem zweiten Beitrag zu regulären Ausdrücken werden wir in Kürze noch einmal auf RegEx in SSIS Skriptkomponenten eingehen und auch einige kleinere Performance-Untersuchungen anstellen.