Archiv verlassen und diese Seite im Standarddesign anzeigen : Logging Daten auswerten (eine Art nicht enthalten Muster)
Hallo alle zusammen,
ich hab wieder mal ein Regex-Problem das mich beschäftigt. Vielleicht kann mir ja jemand einen Tip geben.
Ich habe folgende vereinfachte Ausgangssituation. Meine Logdatei sieht in etwa so aus.
(1) text
text
text
(2) text
text
(3) text
TREFFER
(4) text
Ich hätte jetzt gerne die ID des Eintrags, der den Text TREFFER enthält (also im obigen Fall die 3). Die Anzahl der Zeilen zwischen den IDs sind unterschiedlich.
Ich habs intuitiv mit folgendem Ausdruck probiert:
\\(([0-9]+)\\).*?TREFFER
Das funktioniert nicht korrekt, da .*? alles trifft, also auch die (id) die zwischendurch da stehen. So bekommt ich bei obiger Eingabe die ID "1" zurück.
In der Theorie bräuchte ich soetwas, das sagt: treffe nach der ID alles, bis auf eine weitere ID, also der Ausdruck ([0-9]+) darf nicht teil des Punktes sein.
Wäre das jetzt nur ein Buchstabe, würd ich es so machen \\(([0-9]+)\\)[^(]*?TREFFER. Aber leider die Klammer auf auch im Text der Zeile auftreten.
Hat jemand vielleicht einen Lösungsansatz? Ist das überhaupt machbar bzw. ein reguläres Problem (regulär müsste es imo sein, da ich mir sopntan ein NFA vorstellen kann, der das abbildet)?
Über jede Antwort würde ich mich freuen.
Mfg silo
PS.: Nachdem ich es nach einiger Zeit mithilfe der Regex nicht geschafft hab, hab ichs "per Hand" programmiert. Das problem ist also gelöst, aber mich interessiert trozdem wie das klappen könnte.
Firefall
02.09.2009, 21:04
\((\d+)\).*?\x0D\x0ATREFFER\x0D\x0AKeine Ahnung, was du fürne Syntax brauchst... Das wär jetzt Perl-Syntax. Ich gehe davon aus, dass deine Zeilenumbrüche CRLF sind. Allenfalls kannst du die \x0D weglassen, falls du nur LF Umbrüche hast. Merke: der Punkt matcht Zeilenumbrüche NICHT. Merke auch: Wenn in deinem Logfile im Text nach der Id nochmal so n Id-artiger Teil steht, wird es nicht (genauer gesagt: falsch) funktionieren.
Hallo Firefall,
danke für die schnelle Antwort.
Richtig, ich hatte ganz vergessen zu erwähnen das ich Java benutze.
Ich glaube ich habe mich nicht ausreichend gut ausgedrückt. Ich versuchs nochmal. Es geht mir gar nicht darum den Zeilenumbruch mit einem \x0D\x0A zu treffen. Der Text oben war nur ein Beispiel.
Die Datei könnte auch so aussehen:
(1) text
(2) text text
text text
text text
text text TREFFER text
text
(3) text
Was wohl ganz entscheident ist und was ich auch vergessen habe zu erwähnen ist das ich die Option Pattern.DOTALL benutze. Newlines werden also doch getroffen.
Das führt quasi zu genau diesem Problem:
Merke auch: Wenn in deinem Logfile im Text nach der Id nochmal so n Id-artiger Teil steht, wird es nicht (genauer gesagt: falsch) funktionieren.
Denn in diesem Fall sind die Ids der Folgezeilen in dem .*? enthalten was genau zu dem erwähntem falschen Ergebnis führt.
Und da wollte ich wissen, ob ich das irgentwie umgehen kann.
Ich bräuchte einen Weg zu sagen;
Treffe .*?, aber das Muster (\d+) darf darin nicht enthalten sein.
Mfg silo
Firefall
02.09.2009, 23:12
Hallo Firefall,
danke für die schnelle Antwort.
Richtig, ich hatte ganz vergessen zu erwähnen das ich Java benutze.
Ich glaube ich habe mich nicht ausreichend gut ausgedrückt. Ich versuchs nochmal. Es geht mir gar nicht darum den Zeilenumbruch mit einem \x0D\x0A zu treffen. Der Text oben war nur ein Beispiel.
Die Datei könnte auch so aussehen:
(1) text
(2) text text
text text
text text
text text TREFFER text
text
(3) text
Was wohl ganz entscheident ist und was ich auch vergessen habe zu erwähnen ist das ich die Option Pattern.DOTALL benutze. Newlines werden also doch getroffen.
Das führt quasi zu genau diesem Problem:
Denn in diesem Fall sind die Ids der Folgezeilen in dem .*? enthalten was genau zu dem erwähntem falschen Ergebnis führt.
Und da wollte ich wissen, ob ich das irgentwie umgehen kann.
Ich bräuchte einen Weg zu sagen;
Treffe .*?, aber das Muster (\d+) darf darin nicht enthalten sein.
Mfg silo
Wenn das wie von dir erwähnt geschieht, dann hat der Parser einen Programmierfehler oder du hast ne Option falsch gesetzt. Das Verhalten, das du beschreibst, wenn ich es denn richtig verstanden habe, geschieht normalerweise wenn dein Ausdruck greedy ist. D.h. du verwendest .* ohne das ?. Naja um deine Frage zu beantworten: Für das (\d+) brauchst du wohl einen negativen Lookahead. Ob Java das bietet, kann ich dir nicht sagen, aber es ist sicher ein gutes Stichwort für Google ;) Ansonsten müsstest du dein Logfile konkret beschreiben, was da ist und was nicht. Sonst ist es relativ schwierig, n Pattern zu erraten, das funktioniert.
Wenn das wie von dir erwähnt geschieht, dann hat der Parser einen Programmierfehler oder du hast ne Option falsch gesetzt. Das Verhalten, das du beschreibst, wenn ich es denn richtig verstanden habe, geschieht normalerweise wenn dein Ausdruck greedy ist. D.h. du verwendest .* ohne das ?.
Ok, ich vereinfache mal das Problem. Ich habe folgenden einzeiligen String.
(1)hallo welt (2) wie gehts? (3) das ist ein TREFFER (4) nichts (5) hier ist noch ein TREFFER!!! (6) ende
Die Zahlen in Klammern sind IDs. Ich hätte gern als Ausgabe die ID des Eintrags, in dem die Zeichenkette TREFFER steht. Also in obigem Fall die 3 und die 5.
Wenn ich nun folgenden Ausdruck auswerte \\((\\d+)\\).*?TREFFER bekomme ich als Ausgabe die IDs 1 und 4. Das Ergebnis bekomme ich weil das .*? auch meine ID Konstrukte (Klammer auf - gefolgt von einer Zahl - gefolgt von einer Klammer zu) trifft.
Lasse ich das Fragezeichen weg wird das Ergebnis halt "noch falscher" da im greedy "Modus" ledigllich die 1 als Treffer angezeigt wird.
Das wollte ich mit meinem vorherigem Post beschreiben.
Naja um deine Frage zu beantworten: Für das (\d+) brauchst du wohl einen negativen Lookahead.
Das ist ein interessanter Ansatz. Einen Lookaround kannte ich noch gar nicht. Aber irgentwie habe ich das nach einigem rumspielen auch nicht hinbekommen.
Wie dem auch sei, ich habe eine Lösung gefunden, die nicht schön ist, aber ebenso einfach wie logisch.
Ich habe mir den oben schon angedachten Automaten aufgemalt und versucht ihn in einen Ausdruck zu giessen.
Die Lösung nach der ich gesucht habe sieht folgendermaßen aus (im Java-Quelltext mit "doppelt" auskommentierten Backslashes).
\\((\\d+)\\)
(?:
(?:[^(]*) |
(?:\\([^\\d]+) |
(?:\\(\\d+[^)])
)
TREFFER
Ich hab ihn noch nicht komplett getestet. Hier und da muss ich noch ein Asteriks einfügen, glaube ich. Aber grundsätzlich ist es das, wonach ich gesucht habe. Das meinte ich übrigens auch im Topic mit "nicht enthalten Muster", also ein: "Treffe zwischen der ID und TREFFER alles bis auf eine weitere ID".
Ich hoffe ich habe mit meiner ungenauen Problembeschreibung nicht für allzuviel irritationen gesorgt. Aber ganz unabhängig davon würde mich noch immer interessieren ob man dieses Problem auch mit dem Lookaround lösen kann. Wenn also jemand auf anhieb eine kürzere und insbesondere schönere Lösung kennt, würde ich mich freuen wenn er sie mir mitteilen würde.
Mfg silo
Mit negative look-ahead könnte mans noch versuchen. Wenn mich nicht alles täuscht würde das dann so aussehn:
\((\d+)\)(?!\(\d+\))TREFFER
Weiss aber auch nicht ob Java das supported (oder ob das so richtig ist, konnte es leider grad nicht testen).
Java kann den Lookaround, aber mit deinem Ausdruck bekomme ich mit der Eingabe aus Post #5 leider keinen Treffer. Wenn ich das richtig verstehe ende ich mit diesem Look-ahead bei dem selben Problem wie oben.
Aber wie gesagt, damit kenne ich mich noch gar nicht aus.
Mfg silo
Firefall
07.09.2009, 22:16
Genchas lookahead braucht noch .*? vor der Zahl.
eViL_oNe
08.09.2009, 09:37
sofern man mit der Logdatei auch noch anderen Schabernack anstellen will außer dem hier beschriebenen Anwendungsfall, würde sich das Schreiben eines eigenen Parsers empfehlen anstatt Regex dafür zu vergewaltigen ;)
Firefall
11.09.2009, 22:08
sofern man mit der Logdatei auch noch anderen Schabernack anstellen will außer dem hier beschriebenen Anwendungsfall, würde sich das Schreiben eines eigenen Parsers empfehlen anstatt Regex dafür zu vergewaltigen ;)
Du parst auch alles, was dir begegnet :D
Genchas lookahead braucht noch .*? vor der Zahl.
Ich möchte wirklich gerne verstehen wie das mit dem Lookahead funktioniert, aber bei mir klappt das nicht.
Mit \((\d+)\).*?(?!\(\d+\))TREFFER bekommt ich ein falsches Ergebnis (1 und 4).
Und mit \((\d+)\)(?!.*?\(\d+\))TREFFERbekomme ich gar kein Treffer.
Wo müsste das .*? denn genau hin?
Mfg silo
Mit Lookarounds wirst da nix.
Probiere es mal diesem RegEx. ;)
\((\d+)\)[^\(]+[^\d]+[^\)]+TREFFER
P.s.
Und wenn du es noch genauer haben willst:
\((\d+)\)[^\(]+[^\w]+[^\d]+[^\w]+[^\)]+[^\w]+TREFFER
Das würde dann sogar ein
(1)hallo welt (2) wie gehts? (3) das ist e(in 5 )TREFFER (4) nichts (5) hier ist noch ein TREFFER!!! (6) ende
korrekt matchen...
Mit Lookarounds wirst da nix.
Probiere es mal diesem RegEx. ;)
...
Und wenn du es noch genauer haben willst:
\((\d+)\)[^\(]+[^\w]+[^\d]+[^\w]+[^\)]+[^\w]+TREFFER
Das würde dann sogar ein
(1)hallo welt (2) wie gehts? (3) das ist e(in 5 )TREFFER (4) nichts (5) hier ist noch ein TREFFER!!! (6) ende
korrekt matchen...
Hallo z3r0x,
schade das das mit dem Lookaround nicht klappt. Hatte gehofft das es eine einfacherer Lösung gibt.
Was den von dir beschriebenen Ausdruck angeht, der ist zwar sehr gut würde aber bei folgender Eingabe nicht korrekt funktionieren.
(1)hallo welt (2) wie gehts? (3) das (5 i)st e(in 5 )TREFFER (4) nichts (5) hier ist noch ein TREFFER!!! (6) ende
Ich glaube ganz korrekt wäre folgende Lösung (leicht angepast aus vorherigem Post).
\((\d+)\)(?:(?:[^(])|(?:\([^\d]+)|(?:\(\d+[^)]))*TREFFER
Yep, so funktioniert es noch genauer.
Musst dir halt auf deinen "worst case"-Fall anpassen.
P.s.:
Trauere den lookarounds nicht hinterher.
Wären in diesem Fall einfach nur Ressourcelastiger als diese Methode.
eViL_oNe
21.09.2009, 01:26
Du parst auch alles, was dir begegnet :D
ist auch viel einfacher zu warten als obskure Regex-Spielereien ;)
sofern man mit der Logdatei auch noch anderen Schabernack anstellen will außer dem hier beschriebenen Anwendungsfall, würde sich das Schreiben eines eigenen Parsers empfehlen anstatt Regex dafür zu vergewaltigen ;)
wie könnte denn eigentlich so ein parser aussehen?
in meiner logdatei steht z.b. auch sehr viel müll drin der mich nicht interessiert. wie filtere ich den denn raus. gibts da ne allgem. herangehensweise. irgentwelche tutorials oder tips fürn "parser selber schreiber"-anfänger??
lg fuji
Meistens generiert man sich einen Parser indem man eine Grammatik in einem für Parsergeneratoren (Yacc, JavaCC) verständlichen Format. Daraus generiert man sich dann einen Parser. Man hat die Möglichkeit Aktionen (Codeteile) an bestimmte Tokens zu knüpfen, ausführliche Lektüre dazu sind Kapitel 2 und 3 von "Compilers: Principles, Techniques and Tools" von Aho et. al.
In diesen Kapiteln wird das Lexing besprochen (erkennen von Tokens) und das Parsing (akzeptieren von einer Reihe von Tokens) in der Theorie und auch wie man sowas mit Lex,Yacc anstellt.
Ansonsten ist eine gängige herangehensweise je nachdem was für eine Art Parser man schreibt zu einer Grammatik zu jedem Nonterminal eine Funktion zu erstellen und die Funktionen rufen sich rekursiv auf.(Recursive Descent, top down Parsing) Oder eben einen PDA(Push Down Automaton) für die Grammatik entwerfen und implementieren, da das jedoch recht umständlich sein kann und automatisiert funktioniert werden eben Parsergeneratoren verwendet. (Es ist schon spaßig genug eine große Grammatik korrekt für einen Parsergenerator zu erstellen, da ist man froh wenn man nicht noch die implementierung vom Parser selber machen muss)
eViL_oNe
02.11.2009, 23:39
so kompliziert muss das meines Erachtens für einen solchen Fall nicht sein.
Pragmatisches Vorgehen für einen Log-Parser in Pseudo-Code:
solange nicht Dateiende erreicht (1)
wenn Zeile relevant (2)
werte Zeile als Log-Zeile aus (3)
sonst
füge Zeile zur letzten Log-Zeile hinzu (4)Das kann man auch ohne Lex/YaCC machen mit paar Zeilen Regex-Code gepaart mit einer Ausleseroutine ;).
(1) das Auslesen einer Datei sollte eigentlich keine großen Probleme darstellen. In den meisten Programmiersprachen gibt es geeignete Ausdrücke um das Auslesen von Dateien mit weniger Code-Zeilen auszuführen. (2) Für die Relevanzbewertung kann ein Regex-Ausdruck dienen, den man auf die Zeile anwendet. Idealerweise definiert man sich Teilausdrücke mit Klammern im Regex, auf die man dann bei der Auswertung (3) zurückgreift. Sollte die Prüfung ein negatives Ergebnis bringen, so kann man bei der vorgestellten Log-Struktur die Zeile als Teil der letzten Log-Zeile betrachten und fügt es dort hinzu (4).
Das Resultat davon ist eine Liste von Log-Zeilen, wobei eine Log-Zeile eine zusammengesetzte Struktur (struct, record, klasse, assoziatives array, hashtable oder was auch immer) ist und aus den einzelnen Bausteinen eines einzelnen Loggings besteht, wie z.B. Zeitstempel, Nachricht, Schweregrad, User etc.
Ein echter Tokenizer lohnt sich meines Erachtens nur dann, sofern man so viele optionale Ausdrücke hat, das sich Regex nicht mehr wartungsfreundlich einsetzen lässt (oder tatsächlich die Ebene der Ch-3-Grammatiken verlässt).
Ein waschechter Parser empfiehlt sich erst bei Ch2-Grammatiken, bei den mir bekannten Logs sind das simplere Strukturen ;).
Ausblick:
Basierend auf einer so gewonnenen Struktur fällt einem die Implementierung der Suche und anderer lustigen Verrenkungen nicht mehr schwer. Bei der Implementierung der Suche empfiehlt es sich, das Auslesen der Log-Datei nicht bis zum Ende, sondern nur bis zum ersten Treffer zu implementieren. Man könnte gar eine graphische Auswertung der Protokolldatei hinzaubern oder das Log-Format in ein anderes umwandeln ;).
so kompliziert muss das meines Erachtens für einen solchen Fall nicht sein.
Er fragte ja nach allgemeiner herangehensweise ;) Ich muss gestehen dass ich den Thread nicht nochmal durchgelesen habe :rolleyes:
vBulletin® v3.8.6, Copyright ©2000-2012, Jelsoft Enterprises Ltd.