Eine IP-Adresse erkennen
(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)
Ein weiteres Beispiel, das wir jetzt wesentlich ausführlicher behandeln, ist das Erkennen von IP-(Internet Protocol-)Adressen: vier durch Punkte getrennte Zahlen, wie etwa 1.2.3.4. Oft werden diese Zahlen wie bei 001.002.003.004 mit führenden Nullen angegeben. Ein simpler Test wäre ˹[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*˼, aber das ist so ungenau, dass sogar ›und dann.....?‹ erkannt wird – die Regex erzwingt keine einzige Ziffer; die einzige Forderung sind drei Punkte, mit Ziffern dazwischen oder auch nicht.
Um das zu verbessern, ersetzen wir zunächst die Sterne durch Pluszeichen, weil jede Zahl aus mindestens einer Ziffer bestehen muss. Damit der String außer der IP-Adresse nichts anderes enthält, umschließen wir den Ausdruck mit ˹^...$˼. Das ergibt:
˹^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$˼
Wenn wir statt der Zeichenklasse ˹[0-9]˼ die Abkürzung ˹\d˼ verwenden, erhalten wir das etwas besser lesbare (Anmerkung: Oder auch nicht – das hängt davon ab, woran man sich gewöhnt hat. In einer komplizierten Regex finde ich ˹\d˼ leichter lesbar als ˹[0-9]˼, aber auf bestimmten Systemen sind die zwei nicht exakt äquivalent. Auf Unicode-fähigen Systemen kann ˹\d˼ auch auf Zifferzeichen außerhalb des ASCII-Codes passen (siehe unter Abkürzungen für Zeichenklassen).) ˹^\d+\.\d+\.\d+\.\d+$˼, aber das lässt noch immer Dinge zu, die keine IP-Adressen sind, wie etwa ›1234.5678.9101112.131415‹ (bei IP-Adressen muss jede Zahl im Bereich 0 – 255 liegen). Dreistellige Zahlen ließen sich mit ˹^\d\d\d\.\d\d\d\.\d\d\d\.\d\d\d$˼ erzwingen, aber das ist des Guten zu viel, weil nun ein- und zweistellige Zahlen (wie bei 1.2.3.4) nicht mehr passen. Wenn der verwendete Regex-Dialekt die {min,max}-Notation kennt, kann man ˹^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$˼ benutzen, sonst gibt es immer die Möglichkeit von ˹\d\d?\d?˼ oder ˹\d(\d\d?)?˼ für jeden Teil. Jede Regex erlaubt eine bis drei Ziffern, aber auf verschiedenen Wegen.
Je nach Anforderung genügt eine der verschiedenen Genauigkeitsstufen. Wer sehr genau sein will, muss sich darum kümmern, dass ˹\d\d\d˼ auch auf 999 passt, was viel größer als 255 und damit als Komponente einer IP-Adresse nicht zulässig ist.
Es gibt verschiedene Möglichkeiten, die Zahl auf den Bereich von 0 bis 255 einzuschränken. Ein dummer Ansatz wäre ˹0|1|2|3|...253|254|255˼, der nicht einmal korrekt ist, weil die Zahlen mit führenden Nullen nicht erwischt werden; also bräuchte man ˹0|00|000|1|01|001|...˼, was vollends lächerlich wäre. Für einen DFA ist es nur darum lächerlich, weil der Ausdruck sehr lang ist – aber er ist genauso effizient wie ein anderer Ausdruck, der dasselbe beschreibt. Bei einem NFA dagegen machen die vielen Alternativen den Ausdruck sehr ineffizient.
Ein realistischerer Ansatz konzentriert sich darauf, welche Ziffern an welcher Stelle zugelassen sind. Bei ein- und zweistelligen Zahlen spielt der Bereich keine Rolle, sie können also mit ˹\d|\d\d˼ abgedeckt werden. Auch dreistellige Zahlen, die mit 0 oder 1 beginnen, sind problemlos – das entspricht Zahlen im erlaubten Bereich von 000 – 199. Es kommt also ˹[01]\d\d˼ dazu, und wir erhalten die Regex ˹\d|\d\d|[01]\d\d˼. Dieser Ausdruck ruft vielleicht Erinnerungen an das Uhrzeit-Beispiel aus Einführung in reguläre Ausdrücke (siehe Lösung 4) oder an das Beispiel mit Kalenderdaten aus Wie Regex-Maschinen arbeiten (siehe den Kasten Ein paar Arten, ein Kalenderdatum zu zerstückeln) wach.
Wenn eine dreistellige Zahl mit einer 2 beginnt, muss die Zahl kleiner als 255 sein, also sind an der zweiten Stelle alle Ziffern kleiner 5 erlaubt. Wenn die zweite Ziffer eine 5 ist, muss die dritte kleiner als 6 sein. Das kann mit ˹2[0-4]\d|25[0-5]˼ ausgedrückt werden.
Das mag zunächst verwirren, aber dieser Ansatz hat durchaus seine Logik. Das Resultat ist ˹\d|\d\d|[01]\d\d|2[0-4]\d|25[0-5]˼. Die ersten drei Alternativen können wir sogar zu einer verschmelzen und erhalten ˹[01]?\d\d?|2[0-4]\d|25[0-5]˼. Dies ist bei einem NFA effizienter, weil jede nicht passende Alternative ein Backtracking auslöst. Wenn wir in der ersten Alternative ˹\d\d?˼ statt ˹\d?\d˼ benutzen, hat ein NFA ein kleines bisschen weniger zu tun, wenn gar keine Ziffer im String auftaucht. Die Analyse überlasse ich Ihnen – ein kleines Testbeispiel sollte den Unterschied klarmachen. In diesem Teil des regulären Ausdrucks gibt es noch mehr Optimierungsmöglichkeiten, aber diese verschiebe ich auf Die Kunst, reguläre Ausdrücke zu schreiben.
Nun haben wir einen Unterausdruck, der auf eine einzelne Zahl zwischen 0 und 255 passt. Diesen können wir in Klammern einpacken und anstelle jedes ˹\d{1,3}˼ in die frühere Regex einsetzen. Das ergibt (auf zwei Zeilen aufgeteilt):
˹^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.
([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$˼
Ein dicker Brocken! War das die Mühe wert? Das müssen Sie aufgrund der gestellten Anforderungen entscheiden. Der Ausdruck erlaubt nur noch syntaktisch korrekte IP-Adressen, aber darunter sind doch noch welche, die semantisch falsch sind, zum Beispiel 0.0.0.0 (es dürfen nicht alle Bytes null sein). Mit einem Lookahead-Konstrukt (siehe unter Lookahead; Lookbehind) könnte man sogar diesen Sonderfall ausschließen und nach dem ˹^˼ ein ˹(?!0+\.0+\.0+\.0+$)˼ einfügen, aber irgendwann müssen Sie sich entscheiden, ob Sie noch spezifischer sein wollen – das Kosten-Nutzen-Verhältnis stimmt dann vielleicht nicht mehr. Manchmal ist es einfacher, der Regex Arbeit abzunehmen. Zum Beispiel könnte man auf die einfachere und klare Regex ˹^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$˼ zurückgreifen und jede Komponente einklammern, so dass die gefundenen Zahlen in $1, $2, $3 und $4 verfügbar werden. Diese können dann mit anderen Mitteln der Programmiersprache auf den erlaubten Bereich geprüft werden.
Die Umgebung richtig einschätzen
Die zwei Anker bei dem obigen Ausdruck sind wichtig, und es ist auch wichtig, das zu verstehen. Ohne sie würde der Ausdruck auch auf ip=72123.3.21.993 passen; wenn ein traditioneller NFA verwendet wird, sogar auf ip=123.3.21.223.
In diesem zweiten Fall wird nicht einmal die ganze letzte Zahl 223 erkannt, obwohl die Regex das zulässt. Nun, es ist erlaubt, aber es gibt in dem Ausdruck nichts (wie einen abschließenden Punkt oder einen Anker), was drei Ziffern erzwingt. Die erste Alternative der letzten Gruppe, ˹[01]?\d\d?˼, findet die ersten zwei Ziffern und ist dann am Ende der Regex angelangt. Wie beim Kalenderdaten-Problem im Kasten Ein paar Arten, ein Kalenderdatum zu zerstückeln können wir aber die Alternativen so umordnen, dass sich der gewünschte Effekt einstellt. Hier bedeutet das, die Alternative für drei Ziffern als erste aufzuführen. Damit werden alle zulässigen dreistelligen Zahlen gefunden, bevor die Maschine zur nächsten Alternative geht. (Bei einem DFA oder einem POSIX-NFA ist ein solches Umordnen natürlich unnötig und auch sinnlos, weil diese ohnehin die Alternative mit dem längsten Treffer auswählen.)
Umgeordnet oder nicht – der erste falsche Treffer bleibt ein Problem. Vielleicht denken Sie jetzt: »Ah! Wortgrenzen-Anker lösen das Problem.« Leider nicht, denn so eine Regex würde noch immer auf Texte wie 1.2.3.4.5.6 passen. Um solche Treffer mittendrin auszuschließen, müssen Sie sicherstellen, dass links und rechts in der Umgebung weder alphanumerische Zeichen noch Punkte vorkommen. Wenn Lookaround unterstützt ist, können Sie die gesamte Regex in ˹(?<![\w.])...(?![\w.])˼ verpacken und so fordern, dass die Zeichen vor bzw. nach dem gewünschten Treffer nicht auf ˹[\w.]˼ passen dürfen. Ohne Lookaround kann je nach Situation auch ˹(^|●)...(●|$)˼ genügen.
<< 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