Regular Expressions?
Wie kann ich diese Regex lazy machen:
\/\/([^-]*?)$
Angewandt auf
NTN/4611Z/400V//TR1//I1_T1
bekomme ich (greedy)
TR1//I1_T1
ich will aber nur das letzte Muster nach einem //, d.h.
I1_T1
Würde mich nicht wundern, wenn dies einfach ginge.
3 Antworten
Keine Ahnung in welcher Sprache Du die RegEx verwenden möchtest. wie auch immer, ohne Grouping (und explizites auswerten der Gruppen) wirst Du wohl nicht auskommen.
Der Ausschluss eines Bindestrichs/Minuszeichen, als Kriterium, ist in einem String, in welchem vorwiegend kein Bindestrich vorkommt, quatsch. (not/negativ-Kriterien sind in Regex schwer zu kontrollieren und immer ein Spiel mit dem Feuer.)
Die von @Waldi2007 erwähnten Lookbehinds fallen auch aus, da diese keine Subexpression akzeptieren.
@wrglbrmpft's Ansatz ist zwar erst mal vielversprechend, allerdings kommt dieser ins Schleudern , wenn statt 'NTN/4611Z/400V//TR1//I1_T1' , ein komplexeres Gebilde mi einzelnen / matchen soll: 'NTN/4611Z/400V//TR1//MPFH1M//L1|2/TI36|1'
Stattdessen würde folgendes mit späterer Auswertung der Gruppen (im Programmcode) verwenden
(\/\/).*\1(.*)$
erklärt:
- (\/\/) fängt das erste '//' und speichert es als Gruppe 1
- .* beliebige Zeichen (muss greedy sein!) bis:
- \1 letzte Wiederholung des Inhalts von Gruppe 1
- (.*) ? speichert das Übrige bis Zeilenende in Gruppe 2
Das Ganze (Group 0) matched immer das gesamte Pattern. Der Trick besteht darin die im Patern definierten Guppen auszuwerten.
Wie man auf die einzelnen Gruppen zugreift, ist eine frage der Programmiersprache...
Powershell:
$Input = 'NTN/4611Z/400V//TR1//MPFH1M|L1|2/TI36|1'
$Pattern = '^.*(\/\/).*\1(.*)$'
$Result = [Regex]::Matches($Input,$Pattern)
$Result.Value #entspricht Group 0
$Result.Groups[2].Value
$Replaced = $Input -replace $Pattern,'$2'
"Ersetzung: {0}"-f $Replaced
pause
C#:
using System;
using System.Text.RegularExpressions;
class Prog{
public static void Main(string[] args){
String input = "NTN/4611Z/400V//TR1//MPFH1M|L1|2/TI36|1";
String pattern = @"^.*(\/\/).*\1(.*)$";
MatchCollection matches = Regex.Matches(input, pattern);
foreach (Match match in matches) {
Console.WriteLine(match.Groups[2].Value);
}
// das Äquivalent zu Powershels -replace-Operator
string replaced = Regex.Replace(input, pattern, "$2");
Console.WriteLine( "Ersetzung: {0}", replaced);
Console.WriteLine("press any key");
Console.ReadKey();
}
}
😄...dann wird es Zeit.
Backreferencing und Balancing Groups sind eine sehr interessante "Spielwiese"
Ist wahrscheinlich schon zu spät, aber die einfachste Möglichkeit, das letzte Vorkommen eines Patterns zu finden, ist, ein Backtracking zu erzwingen. Also du konsumierst mit .* erstmal alle Zeichen und sagst dann, dass du zwei Slashes suchst und alles, was danach kommt, matchen willst. Also so (in .NET, also z.B. C# oder PowerShell):
.*//(.*)$
Ich weiß ehrlich gesagt nicht, warum du [^-] benutzt. Das würde bei einem Bindestrich im letzten Element nur dazu führen, dass gar nichts gematcht wird. Falls das so sein soll, kannst du es natürlich so einbauen, ansonsten kannst du einfach mit .* alles matchen, was nach den zwei Slashes kommt.
MIt diesem lazy-Operator (z.B. [^-]*?) kannst du nur von links nach rechts matchen, also in deinem Fall versucht das Programm, das Muster so früh wie möglich zu beenden, aber eben nicht, so spät wie möglich anzufangen.
Als Alternative könntest du auch den String umdrehen, dann nach den Zeichen bis zum ersten Doppelslash suchen und dann das Ergebnis auch wieder umdrehen, also etwa so:
var result = Reverse(Regex.Match(Reverse("NTN/4611Z/400V//TR1//I1_T1"), "^(.*?)//").Groups[1].Value);
Da funktioniert dann auch der lazy-Operator, aber die erste Variante dürfte sinnvoller und einfacher sein.
Da beide Antworten die Bedeutung des Regex ändern, hier eine Lösung, die gleich matcht wie Dein eigener Regex:
\/\/[^-]*\/\/([^-]*)$
Du matchst erst alle Vorkommen und nimmst dann das letzte Vorkommen auf.
Das Problem bei \/\/([^\/]*)$ ist, dass dort Bindestriche kein Ausschlusskriterium mehr sind, während ein einfacher / zum Ausschlusskriterium wird.
Btw: ein ? nach einem * braucht man nicht, da * immer auch null Vorkommen matcht. Nur ein + matcht nur bei mindestens einem Vorkommen
Danke - das mit der Wiederholung von Gruppen (\1) habe ich nicht gekannt. Das erleichert die Sache ungemein.