Multiline - Regex mit Ersetzung

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

Hallo zusammen!

Ich stoße bei der Verwendung von regulären Ausdrücken gerade an eine Verständnissgrenze:
Ich muss in einer Textdatei nach einer Passage suchen, welche sich ggf. über mehrere Zeilen erstreckt. Ich habe mir dafür auch einen wunderbaren Regex gebaut, der in https://regex101.com/ wunderbar mit den bereitgestellten Testdaten funktioniert, allerdings kriege ich das nicht in meinen Code integriert :P

Ich habe eine Textdatei, in welcher ich mehrzeilig nach einem Regex-Treffer suchen muss. Das probiere ich, indem ich den Inhalt einer Datei zunächst komplett in eine Variable lese und den regex anschließend darauf anwende. Klappt aber nicht, so wie ich mir das vorstelle.
Ich habe das Problem mal folgendermaßen auf einen leichter verdaulichen Happen heruntergebrochen:

Code: Alles auswählen

import re

with open('testdata.txt') as myfile:
    content = myfile.read()
print(content)
print("=================")
print(re.match(r'^Test$', content))
print(re.match(r'^Test$', content, flags=re.MULTILINE))
print(re.match(r'^(?:\n|.*)*Test(?:\n|.*)*$', content))
print(re.match(r'^(?:\n|.*)*Test(?:\n|.*)*$', content, flags=re.MULTILINE))
Das Ergebnis:

Code: Alles auswählen

$ python3.6 demo.py 
Dieses ist
ein
Test
foobar
=================
None
None
<_sre.SRE_Match object; span=(0, 26), match='Dieses ist\nein\nTest\nfoobar'>
<_sre.SRE_Match object; span=(0, 26), match='Dieses ist\nein\nTest\nfoobar'>
$
... verstehe ich nicht:
  1. Nach allem, was ich über reguläre Ausdrücke weiß, markiert "^" in dieser Syntax den Zeilenanfang, wenn man im Multiline modus arbeitet; sonst trifft der den Anfang des gesamten Strings, samt seiner möglichen Zeilenumbrüche. Der erste Regex (r'^Test$') sollte demnach auf eine Zeile passen, die genau "Test" enthält; tut es aber nicht. Wird demnach wohl als einzige, lange Zeile mit Zeilenumbruchzeichen darin behandelt. Also gibt es keinen String der mit "Test" anfängt und endet - matched nicht: Kann ich nachvollziehen. Also: Irgendwie multiline matching aktivieren.
  2. Kurz in die Doku geschaut, was das re.MULTILINE flag tut und so wie ich es verstehe, sollte das Flag das von mir erwartete Verhalten bringen (zu matchen) - tut es aber ebenfalls nicht.
  3. Erst, wenn ich den Inhalt von "content" als einen einzigen, langen String betrachte und manuell bestimme, das der String neben beliebig vielen beliebigen Zeichen auch beliebig viele Zeilenumbrüche vor und nach "Test" haben darf, matched das; unabhängig davon ob das re.MULTILINE flag gesetzt ist oder nicht.
Erste Frage: Welchen Sinn hat denn dann das re.MULTILINE Flag, bzw. was mache ich bei seiner Verwendung falsch?
Zweite Frage: Sollte dieses der Falsche Weg sein Multiline-Matching in einer Datei anzuwenden: Wie sieht denn dann bitte der korrekte aus?

LG
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du scheinst match mit search zu verwechseln. Warum sollte denn ploetzlich MULTILINE aus ersterem letzteres machen? match will immer *ALLES* matchen, da musst du eben auch pre/postfixe angeben.

Wenn du eh zeilenweise arbeiten willst, dann iterier doch ueber die Zeilen & spar dir das mulitline-Gefummel.
Benutzeravatar
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

Der __deets__ wieder als erster ... Du bist echt schnell! ;)

In der Tat, ich scheine .match mit .search zu verwechseln, vielen Dank für den Hinweis!
Bedeutet die Doku das, das re.M das Verhalten von re.match in keinster Weise beeinflusst?
__deets__ hat geschrieben:Wenn du eh zeilenweise arbeiten willst, dann iterier doch ueber die Zeilen & spar dir das mulitline-Gefummel.
In diesem Fall mag das klappen, aber wie in der Einleitung geschrieben, möchte ich im eigentlichen Programm eher folgendes machen:
Ich möchte die Seriennummer einer Zone Datei hochzählen; diese Zeile kann ja über mehrere Zeilen gehen, so das eine Zonedatei in etwa wie folgt aussehen kann:

Code: Alles auswählen

$ORIGIN .
$TTL 120	; 2 minutes
;$TTL 3600	; 1 hour
test.mut.zen-net.internal	IN SOA	ns.zen-net.internal. helpdesk.support.zen-net.de. (
				1575 ; serial
				800        ; refresh (30 minutes)
				60         ; retry (1 minute)
				3600000    ; expire (1000 hours)
				3600       ; minimum (1 hour)
				)
Wie gesagt: die serial (hier: 5. Zeile) möchte ich hochzählen. Da "; serial" ein optionaler kommentar in dieser Zeile ist, habe ich ja nur eine Zahl als Anhaltspunkt; daher muss ich ja zuvor gucken ob der Kontext passt, sprich: ob in der/den Zeilen zuvor der SOA Record eingeleitet wurde.
Dazu muss ich ja über mehrere Zeilen gehen; sonst fallen mir nur Methoden ein, bei denen ich X regexe miteinander verknüpfen muss, wie:

Wenn die aktuelle Zeile dem SOA record entspricht, dann
prüfe, ob diese die Seriennummer enthält, sonst
solange bis die Seriennummer festgestellt wurde:
prüfe ob die nächste Zeile die seriennummer enthält

Ich dachte, das geht schicker ;)

Bestimmt übersieht der N00b gerade wieder etwas vollkommen offensichtliches.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Doch, M beeinflusst schon. Es aendert halt die Bedeutung des ^ und auch $ - siehe zB

Code: Alles auswählen

import re
data = """abcd
efgh
ijkl"""
print(re.match(r"(^.*$)+", data, re.M).group(0))
print(re.match(r"(^.*$)+", data, re.DOTALL).group(0))
Das zweite match geht nicht ohne DOTALL, weil $ dann nicht das Zeilenende ist, sondern das String-Ende, und dann muss man explizit mit DOTALL (oder entsprechend \n im Ausdruck) ueber die Zeilenenden rueber stiefeln.

Was deine Datei angeht - da wuerde ich einfach mal einen Parser-Generator drauf anwerfen. pyparsing zB.
Benutzeravatar
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

Danke für Deine Erklärungen - hat mir sehr geholfen!
Ich mache es jetzt mit .search auf den kompletten content.
Antworten