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

In einem Suchmuster repräsentiert jedes Zeichen sich selbst, ausgenommen die Spezialzeichen (magic characters). Diese sind ^$()%.[]*+-?). Spezialzeichen in Suchmustern müssen escapiert werden, indem ein % davor gesetzt wird, ausgenommen ", das mit \" escapiert wird. Auch Sonderzeichen, die keine Spezialzeichen sind, können escapiert werden. Alphanumerische Zeichen sollten nicht escapiert werden.

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
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 ?.

Wiederholungszeichen

Diese Spezialzeichen geben an, wie oft ein Suchmuster vorkommen soll oder muß.

Zeichen Bedeutung
kein Zusatz das Zeichen oder die Zeichenklasse selbst, genau einmal
* 0 oder mehr Wiederholungen, längst mögliche Zeichenfolge (greedy)
+ mindestens eine Wiederholung, längst mögliche Zeichenfolge (greedy)
- 0 oder mehr Wiederholungen, kürzest mögliche Zeichenfolge (nongreedy)
? 0 oder ein Mal (für optionale Zeichen z.B.)
%n n zwischen 1 und 9, für das n.-te Suchergebnis
%bxy x und y sind zwei verschiedene Zeichen, Suchstring beginnt mit x und endet mit y (balanced), %b() liefert Zeichen zwischen Klammern

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.

Zahlen in Exponentialdarstellung umwandeln

Manche Programme kennen keine Exponentialdarstellung der Form 4.5e+2, z.B. der path-Befehl von Lilypond.

Folgender Code wandelt alle Exponentialzahlen in Kommazahlen um.

Das Muster "[-+]?%d*%.?%d+[eE][-+]%d+" bedeutet: ein optionales Vorzeichen, gefolgt von 0-n Ziffern, optionaler Kommapunkt, mindestens eine Ziffer, e oder E, Vorzeichen und mindestens eine Ziffer

Warum in zwei Schritten? Mir ist es noch nicht gelungen, ein Muster zu entwerfen, das beide Möglichkeiten abdeckt, wahrscheinlich bedingt durch die Einschränkungen von regulären Ausdrücken bei Lua.

pfad="10e-3 15 .5e+3 -20-2.4+77-3.5e+4"
local converted
local exppat = "[-+]?%d*%.?%d*[eE][-+]%d+"
local numpattern = "[-+]?%d*%.?%d+"
numbers = {}

print(pfad)
converted=pfad:gsub(exppat, function(w) return w*1 .. " " end)
print(converted)

for n in converted:gmatch(numpattern) do
   table.insert(numbers, n)
end

print(table.concat(numbers,', '))

-- OUTPUT
--10e-3 15 .5e+3 -20-2.4+77-3.5e+4
--0.01  15 500.0  -20-2.4+77-35000.0 
--0.01, 15, 500.0, -20, -2.4, 77, -35000.0

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.

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