Reguläre Ausdrücke sind eine äußerst vielseitige Möglichkeit, Strings mit einem Muster abzugleichen. Dazu verwendest du eine domänenspezifische Sprache (DSL), die genau für diesen Zweck entwickelt wurde.
Wie einige andere Programmiersprachen versucht Julia nicht, eine eigene Regex-Bibliothek zu implementieren. Stattdessen nutzt es die beliebte Bibliothek PCRE2 und bietet damit eine Regex-Syntax, die identisch mit der von (zum Beispiel) Gleam und sehr ähnlich zu JavaScript ist.
Dieser Julia-Lehrplan geht davon aus, dass du bereits mit der grundlegenden Regex-Syntax vertraut bist. Wir konzentrieren uns ausschließlich auf Julia-spezifische Merkmale.
Unten findest du einige Ressourcen, mit denen du dein Wissen über reguläre Ausdrücke auffrischen kannst.
Julias Schnittstelle zu regulären Ausdrücken wird im Handbuch beschrieben.
Ein regulärer Ausdruck in Julia ist einfach ein String, dem ein r vor dem öffnenden " vorangestellt ist. Die gesamte grundlegende Funktionalität ist Teil der Standardbibliothek.
Tatsächlich sind viele der Funktionen, die bereits im Konzept Strings besprochen wurden, standardmäßig für Regex-Suchen ausgelegt, zum Beispiel occursin().
julia> re = r"test$"
r"test$"
julia> typeof(re)
Regex
# Does a string end with "test"?
julia> occursin(re, "this is a test")
true
julia> occursin(re, "these are tests")
false
Modifikatorzeichen können auf das schließende Anführungszeichen folgen, zum Beispiel i für eine Übereinstimmung ohne Beachtung der Groß-/Kleinschreibung.
julia> occursin(r"test", "Testing")
false
julia> occursin(r"test"i, "Testing")
true
Häufig wollen wir wissen, was übereinstimmt. Dazu fügst du Erfassungsgruppen in Klammern in den regulären Ausdruck ein und verwendest dann die Funktion match().
julia> m = match(r"(\d+g) .* (\d+ml)", "dissolve 25g sugar in 200ml water")
RegexMatch("25g sugar in 200ml", 1="25g", 2="200ml")
julia> m.captures
2-element Vector{Union{Nothing, SubString{String}}}:
"25g"
"200ml"
# how many matches?
julia> length(m.captures)
2
# what matched?
julia> m[1], m[2]
("25g", "200ml")
# Starting positions of the matches (character index)
julia> m.offsets
2-element Vector{Int64}:
10
23
Natürlich können Übereinstimmungen fehlschlagen. Das Ergebnis ist dann der spezielle Wert Nothing anstelle eines RegexMatch. Sei also darauf vorbereitet, das zu prüfen.
# failed match
m = match(r"(not here)", "dissolve 25g sugar in 200ml water")
julia> typeof(m)
Nothing
julia> isnothing(m)
true
Obwohl match standardmäßig am Anfang des Strings beginnt, kannst du auch einen Offset n angeben, um die ersten n Zeichen zu ignorieren.
# capture first match
julia> m = match(r"(\wat)", "cat, sat, mat")
RegexMatch("cat", 1="cat")
# ignore first 5 characters, then match
julia> m = match(r"(\wat)", "cat, sat, mat", 5)
RegexMatch("sat", 1="sat")
In Julia findet match() nur die erste Übereinstimmung im Ziel-String: Es gibt keinen globalen Modifikator wie in einigen anderen Sprachen.
Stattdessen gibt es eachmatch(), das einen Iterator über Übereinstimmungen zurückgibt. Dieser wird verzögert ausgewertet, du musst ihn also möglicherweise in das gewünschte Format umwandeln.
julia> matches = eachmatch(r"(\wat)", "cat, sat, mat")
Base.RegexMatchIterator{String}(r"(\wat)", "cat, sat, mat", false)
# convert to vector
julia> collect(matches)
3-element Vector{RegexMatch}:
RegexMatch("cat", 1="cat")
RegexMatch("sat", 1="sat")
RegexMatch("mat", 1="mat")
# convert with comprehension
julia> [m.match for m in matches]
3-element Vector{SubString{String}}:
"cat"
"sat"
"mat"
# broadcast an anonymous function
julia> (m -> m.match).(matches)
3-element Vector{SubString{String}}:
"cat"
"sat"
"mat"
Überlappende Übereinstimmungen sind standardmäßig nicht erlaubt. Füge overlap = true als Schlüsselwortargument hinzu, um das zu ändern.
julia> eachmatch(r"aba", "abababa") |> collect # matches at positions 1, 5
2-element Vector{RegexMatch}:
RegexMatch("aba")
RegexMatch("aba")
julia> eachmatch(r"aba", "abababa"; overlap = true) |> collect # also matches at position 3
3-element Vector{RegexMatch}:
RegexMatch("aba")
RegexMatch("aba")
RegexMatch("aba")
Ein häufiger Grund für die Verwendung eines regulären Ausdrucks ist es, die Übereinstimmung durch einen anderen String zu ersetzen.
Die Funktion replace() wurde im Konzept Strings besprochen, wobei String-Literale zum Suchen verwendet wurden. Dieselbe Funktion kann die volle Leistungsfähigkeit des Regex-Abgleichs nutzen.
julia> replace("some string", r"[aeiou]" => "*")
"s*m* str*ng"
julia> replace("first second", r"(\w+) (?<agroup>\w+)" => s"\g<agroup> \1")
"second first"
Das zweite Beispiel oben zeigt, wie sowohl nummerierte als auch benannte Erfassungsgruppen in der Ersetzung verwendet werden können, und zwar innerhalb eines s" "-Strings.
Weitere Details findest du im Handbuch. Dieses Thema zwingt die meisten Programmierer ständig dazu, wieder in die Dokumentation zu schauen!