Lua Patterns and Captures

Zur Navigation springen Zur Suche springen

Intro

Die Zeichenketten-Programmbibliothek von Lua enthält einige Funktionen, die mit regulären Ausdrücken funktionieren, genauer gesagt mit einer Teilmenge davon. Reguläre Ausdrücke werden in vielen Sprachen verwendet. Auf dieser Seite wird erklärt, wie man reguläre Ausdrücke in Lua nutzbringend anwenden kann.

Folgende Lua-Funktionen unterstützen RegEx:

string.find (s, pattern [, init [, plain]])
string.gmatch (s, pattern)
string.gsub (s, pattern, repl [, n])
string.match (s, pattern [, init])

Technische Dokumentation Lua-Patterns:

Pattern Matching

In den folgenden Beispielen verwenden wir die Funktion string.match. Diese Funktion ermöglicht es uns, ein Muster anzugeben und liefert den Teil der Zeichenkette zurück, der mit dem Suchmuster übereinstimmt oder nil, falls keine Übereinstimmung gefunden wurde.

Wenn wir die Zeichenkette hello haben und wir suchen nach dem Muster el, werden wir als Ergebnis el erhalten (oder nil, falls wir uns vertippt haben):
s = "hello"
print(s:match("el")) --ergibt "el"
s = "hallo"
print(s:match("el")) --ergibt nil

Das Ergebnis haut uns nicht unbedingt vom Hocker.

Spezialzeichen

Es wird interessanter, wenn wir Spezialzeichen verwenden (magic characters). Das elementarste Zeichen ist der Punkt ., der für jedes beliebige Zeichen steht.
s = "hello"
print(s:match("h.l")) --liefert "hel"
s = "hallo"
print(s:match("h.l")) --liefert "hal"

Ein weiteres Spezialzeichen ist das Fragezeichen ?, welches verlangt, dass das vorhergehende Zeichen 0 oder 1 Mal vorkommt. Dadurch wird dieses Zeichen optional, weil es nicht unbedingt vorkommen muss.

Wenn wir sowohl hello als auch helo als Ergebnis haben wollen, können wir ein Muster als hell?o festlegen. Dadurch wird das zweite l optional:
s = "hello"
print(s:match("hell?o")) --liefert "hello"
s = "helo"
print(s:match("hell?o")) --liefert "helo"
s = "heo"
print(s:match("hell?o")) --liefert nil
?: vorangestelltes Zeichen 0 oder 1 Mal (optional, Länge 1)
+: vorangestelltes Zeichen beliebig oft, mindestens 1 Mal (erforderlich)
*: vorangestelltes Zeichen beliebig oft oder gar nicht (optional, beliebige Länge

Das Suchmuster hel+o würde mit helo matchen, wobei l beliebig oft vorkommen kann, jedoch mindestens einmal vorhanden sein muss. hel*o passt auch mit heo zusammen, wo überhaupt kein l vorkommt.

Natürlich kann man diese Suchmuster miteinander kombinieren.
s = "hello!"
print(s:match("h.+o!?")) --liefert "hello!"
Das obenstehende Suchmuster matcht jeden Text, der h und o mit mindestens einem Zeichen dazwischen enthält, gefolgt von einem optionalen Ausrufezeichen. (Beachte, dass ! kein Spezialzeichen ist).

Zeichenklassen

Außer dem Punkt . (der für ein beliebiges Zeichen steht) gibt es weitere einschränkende Unterklassen:

Lua Dokumentation:

%a: Buchstaben (a-z, A-Z)
%c: Steuerzeichen (Null, Tab, Carr.Return, Linefeed, Delete, ...)
%d: Ziffern (0-9)
%l: Kleinbuchstaben (a-z)
%u: Großbuchstaben (A-Z)
%p: Satzzeichen (. , ? ! : ; @ [ ] _ { } ~)
%s: Weißraumzeichen (Tab, Carr.Return, Linefeed, Leerzeichen)
%w: alphanumerische Zeichen (a-z, A-Z, 0-9)
%x: Hexadezimalzahlen (0-9, a-f,  A-F)
%z: Nullzeichenfolge \000
BulB.svg
Achtung, Umlaute gehören nicht zu den alphanumerischen Zeichen!

Das Suchmuster he%l%lo matcht jeden Teilstring, der he und o mit zwei Kleinbuchstaben dazwischen enthält.

Du kannst auch mehrere Zeichenklassen miteinander kombinieren, indem sie in eckige Klammern eingeschlossen werden [...]. Wenn das erste Zeichen nach der eckigen Klammeer ein ^ ist, wird die Bedeutung umgekehrt, d.h. es werden alle Zeichen ausgeschlossen, die in der definierten Klasse enthalten sind.

Beispielsweise muss in den meisten Programmiersprachen ein gültiger Variablenname mit einem Buchstaben oder Unterstrich beginnen und kann eine beliebige Zahl von nachfolgenden Buchstaben, Ziffern oder Unterstrichen enthalten. Das sieht als Lua-Pattern folgendermaßen aus:
s = "_myvar91"
if s:match("[%a_]+[%a%d_]*") then
  print("valid identifier!")
end
% steht als Escapezeichen in Lua RegEx-Mustern, daher matcht %?+ eines oder mehrere Fragezeichen, die Zeichenfolge %? steht für das Ausrufezeichen selbst ?.

Wiki-Seitennamen aus Zeichenkette extrahieren

Folgende Zeichen dürfen nicht in gültigen MediaWiki-Seitennamen enthalten sein: # < > [ ] | { }

Ein Pattern, das sämtliche in Seitennamen nicht erlaubten Zeichen enthält, könnte daher folgendermaßen ausschauen:
b=a:match('[%[%#%]%<%>%{%}%|%c]*')

Dieses Pattern liefert einen gültigen Seitennamen, der von ungültigen Zeichen umschlossen ist:

a:match('[%[%#%]%<%>%{%}%|%c]*([^%[%#%]%<%>%{%}%|%c>]*)')
-- Anwendungsbeispiel
a='[[#{}|{{<cc-c##xx>'
a:match('[%[%#%]%<%>%{%}%|%c]*(%w*)')                     --> cc
a:match('[%[%#%]%<%>%{%}%|%c]*([^%[%#%]%<%>%{%}%|%c>]*)') --> cc-c
Die erste Variante, die nur alphanumerische Zeichen berücksichtigt, würde nicht das gewünschte Ergebnis liefern, sobald eines der erlaubten Zeichen ()- ..., das jedoch kein alphanumerisches Zeichen ist, im Seitennamen enthalten ist.

Anker

Es gibt zwei weitere Zeichen, deren Bedeutung bisher nicht erwähnt wurde:

  • ^: kennzeichnet den Beginn einer Zeichenkette (string)
  • $: kennzeichnet das Ende einer Zeichenkette
In den darüberstehenden Beispielen habe wir eine längere Zeichenkette betrachtet, um kürzere Übereinstimmungen zu finden. Das Suchmuster hallo kann in einem längeren String enthalten sein, etwa „er sagte hallo und ging wieder“. Das Muster ^hallo$ stimmt nur mit hallo und nichts sonst überein (vorangestellte oder nachfolgende Zeichen sind nicht erlaubt!). Wir könnten sagen, dass wir das Muster an den Beginn und an das Ende der Zeichenkette verankert haben.
s = "hello world!"
print(s:match("^he..")) --ergibt "hell"
s = "123 hello world!"
print(s:match("^he..")) --ergibt nil

Captures

Nun kommen wir zum nützlichsten Merkmal von RegEx-Mustern: Captures (auch Deutsch etwa: Zeichenerfassung).

Ein Capture wird durch runde Klammern definiert. Damit können wir angeben, welchen Teil oder welche Teile des Musters uns string.match als Ergebnis liefern soll.
s = "abc"
print(s:match("a.c"))   --ergibt "abc"
print(s:match("a(.)c")) --ergibt "b"
Die Zeichenkette abc entspricht beiden Suchmustern. Das zweite Pattern jedoch verwendet ein Capture, um auszudrücken, dass wir nur an dem einzelnen Zeichen zwischen a und c interessiert sind. Wir wollen weder a noch c im Ergebnis haben. Wir können außerdem mehrere Captures festlegen und erhalten sie als geordnetes Resultat.
m, d, y = "04/19/64":match("(%d+)%p+(%d+)%p+(%d+)") -- %d: Zahl %p: Satzzeichen
-- m: 04
-- d: 19
-- y: 64
pair = "name = Anna"
_, _, key, value = string.find(pair, "(%a+)%s*=%s*(%a+)")
print(key, value)  --> name  Anna

Erklärung der Suchmuster:

%d+
mindestens eine Ziffer
%s
ein einzelnes Leerzeichen
[%d%.]+
ein oder mehr Zahlen oder Punkte
a+
mindestens ein Buchstabe
p+
mindestens ein Satzzeichen
Der Unterschied zwischen string.find und string.match ist der, dass string.find als erstes die Stelle, wo die Fundstelle beginnt und als zweites das Ende der Fundstelle liefert. Diese beiden Variablen werden mit _, _, quasi übersprungen.

Schlussfolgerung

Aufgrund von Dateigrößen- und Speicherplatzbeschränkungen unterstützt Lua nur einen Teil der RegEx-Syntax. Trotzdem kann man mit den verfügbaren Ausdrücken eine Menge nützerliche Dinge anstellen. Wenn die Anforderungen zu komplex ist, kann man eine Zeichenkette in Einzelteile zerlegen und sie gesondert parsen.

Der einzige Nachteil ist, dass der Programmcode für jemanden, der sich mit RegEx nicht auskennt, praktisch unlesbar wird. Daher füge stets ausreichend viele Kommentare hinzu, das hilft auch, wenn du den Code später nochmals ändern willst.

Links