13 Stimmen

Python: Regexp in einer Datei finden

Haben:

f = open(...)  
r = re.compile(...)

Bedarf:
Die Position (Anfang und Ende) einer ersten passenden Regexp in einer großen Datei finden?
(ausgehend von current_pos=... )

Wie kann ich das tun?


Ich möchte diese Funktion haben:

def find_first_regex_in_file(f, regexp, start_pos=0):  
   f.seek(start_pos)  

   .... (searching f for regexp starting from start_pos) HOW?  

   return [match_start, match_end]  

Die Datei 'f' wird voraussichtlich groß sein.

33voto

Greg Hewgill Punkte 882617

Eine Möglichkeit, große Dateien zu durchsuchen, ist die Verwendung der mmap Bibliothek, um die Datei in einen großen Speicherbereich abzubilden. Dann kann man sie durchsuchen, ohne sie explizit lesen zu müssen.

Zum Beispiel so etwas wie:

size = os.stat(fn).st_size
f = open(fn)
data = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ)

m = re.search(r"867-?5309", data)

Das funktioniert gut bei sehr großen Dateien (ich habe es bei einer Datei von 30+ GB gemacht, aber Sie brauchen ein 64-Bit-Betriebssystem, wenn Ihre Datei mehr als ein oder zwei GB groß ist).

4voto

Adeel Zafar Soomro Punkte 1472

Der folgende Code funktioniert einigermaßen gut mit Testdateien von etwa 2 GB Größe.

def search_file(pattern, filename, offset=0):
    with open(filename) as f:
        f.seek(offset)
        for line in f:
            m = pattern.search(line)
            if m:
                search_offset = f.tell() - len(line) - 1
                return search_offset + m.start(), search_offset + m.end()

Beachten Sie, dass sich der reguläre Ausdruck nicht über mehrere Zeilen erstrecken darf.

0voto

parity3 Punkte 603

HINWEIS: Dies wurde mit Python 2.7 getestet. Möglicherweise müssen Sie die Dinge in Python 3 zwicken, um Strings vs. Bytes zu behandeln, aber es sollte nicht zu schmerzhaft hoffentlich sein.

Memory-Mapped-Dateien sind möglicherweise nicht ideal für Ihre Situation (32-Bit-Modus erhöht die Wahrscheinlichkeit, dass nicht genügend zusammenhängender virtueller Speicher vorhanden ist, dass nicht aus Pipes oder anderen Nicht-Dateien gelesen werden kann usw.).

Hier ist eine Lösung, die 128k-Blöcke auf einmal liest. Solange Ihre Regex mit einer Zeichenkette übereinstimmt, die kleiner als diese Größe ist, wird dies funktionieren. Beachten Sie auch, dass Sie nicht durch die Verwendung von einzeiligen Regexen eingeschränkt sind. Diese Lösung funktioniert ziemlich schnell, obwohl ich vermute, dass sie geringfügig langsamer ist als die Verwendung von mmap. Es hängt wahrscheinlich mehr davon ab, was Sie mit den Übereinstimmungen machen, sowie die Größe/Komplexität der Regex, nach der Sie suchen.

Die Methode stellt sicher, dass nur maximal 2 Blöcke im Speicher gehalten werden. In einigen Anwendungsfällen kann es sinnvoll sein, mindestens eine Übereinstimmung pro Block zu erzwingen, aber diese Methode kürzt ab, um die maximale Anzahl von 2 Blöcken im Speicher zu halten. Sie stellt auch sicher, dass jede Regex-Übereinstimmung, die bis zum Ende des aktuellen Blocks reicht, NICHT ausgegeben wird und stattdessen die letzte Position für den Fall gespeichert wird, dass entweder die wahre Eingabe erschöpft ist oder wir einen anderen Block haben, auf den die Regex vor dem Ende passt, um Muster wie "[^ \n ]+" oder "xxx$". Sie können zwar immer noch etwas kaputt machen, wenn Sie am Ende der Regex eine Vorausschau wie xx(?!xyz) haben, wobei yz im nächsten Block steht, aber in den meisten Fällen können Sie solche Muster umgehen.

import re

def regex_stream(regex,stream,block_size=128*1024):
    stream_read=stream.read
    finditer=regex.finditer
    block=stream_read(block_size)
    if not block:
        return
    lastpos = 0
    for mo in finditer(block):
        if mo.end()!=len(block):
            yield mo
            lastpos = mo.end()
        else:
            break
    while True:
        new_buffer = stream_read(block_size)
        if not new_buffer:
            break
        if lastpos:
            size_to_append=len(block)-lastpos
            if size_to_append > block_size:
                block='%s%s'%(block[-block_size:],new_buffer)
            else:
                block='%s%s'%(block[lastpos:],new_buffer)
        else:
            size_to_append=len(block)
            if size_to_append > block_size:
                block='%s%s'%(block[-block_size:],new_buffer)
            else:
                block='%s%s'%(block,new_buffer)
        lastpos = 0
        for mo in finditer(block):
            if mo.end()!=len(block):
                yield mo
                lastpos = mo.end()
            else:
                break
    if lastpos:
        block=block[lastpos:]
    for mo in finditer(block):
        yield mo

Zum Testen/Erkunden können Sie dies ausführen:

# NOTE: you can substitute a real file stream here for t_in but using this as a test
t_in=cStringIO.StringIO('testing this is a 1regexxx\nanother 2regexx\nmore 3regexes')
block_size=len('testing this is a regex')
re_pattern=re.compile(r'\dregex+',re.DOTALL)
for match_obj in regex_stream(re_pattern,t_in,block_size=block_size):
    print 'found regex in block of len %s/%s: "%s[[[%s]]]%s"'%(
        len(match_obj.string),
        block_size,match_obj.string[:match_obj.start()].encode('string_escape'),
        match_obj.group(),
        match_obj.string[match_obj.end():].encode('string_escape'))

Hier ist die Ausgabe:

found regex in block of len 46/23: "testing this is a [[[1regexxx]]]\nanother 2regexx\nmor"
found regex in block of len 46/23: "testing this is a 1regexxx\nanother [[[2regexx]]]\nmor"
found regex in block of len 14/23: "\nmore [[[3regex]]]es"

Dies kann in Verbindung mit Quick-Parsing eine große XML nützlich sein, wo es in Mini-DOMs auf der Grundlage eines Unterelements als Root aufgeteilt werden kann, anstatt in die Handhabung von Callbacks und Zuständen bei der Verwendung eines SAX-Parsers eintauchen zu müssen. Es erlaubt auch, durch XML schneller als gut zu filtern. Aber ich habe es auch für viele andere Zwecke verwendet. Ich bin etwas überrascht, dass solche Rezepte nicht häufiger im Netz zu finden sind!

Noch eine Sache: Das Parsen in Unicode sollte funktionieren, solange der übergebene Stream Unicode-Strings erzeugt, und wenn Sie die Zeichenklassen wie \w müssen Sie das Flag re.U zur re.compile-Musterkonstruktion hinzufügen. In diesem Fall bedeutet block_size tatsächlich die Anzahl der Zeichen und nicht die Anzahl der Bytes.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X