Python: найти регулярное выражение в файле

Есть:

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

Необходимость:
Найти позицию (начало и конец) первого соответствующего регулярного выражения в большом файле?
(начиная с current_pos=...)

Как я могу это сделать?


Я хочу иметь эту функцию:

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]  

Файл 'f' ожидается большим.

3 ответа

Решение

Одним из способов поиска больших файлов является использование mmap библиотека для отображения файла в большой кусок памяти. Тогда вы можете искать через него, не имея явного чтения.

Например, что-то вроде:

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)

Это хорошо работает для очень больших файлов (я сделал это для файла размером более 30 ГБ, но вам понадобится 64-битная ОС, если ваш файл больше, чем ГБ или два).

Следующий код работает достаточно хорошо с тестовыми файлами размером около 2 ГБ.

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()

Обратите внимание, что регулярное выражение не должно занимать несколько строк.

ПРИМЕЧАНИЕ: это было проверено на python2.7. Возможно, вам придется настроить вещи в Python 3 для обработки строк и байтов, но, надеюсь, это не должно быть слишком болезненным.

Файлы, отображенные в память, могут не подходить для вашей ситуации (32-битный режим увеличивает вероятность того, что не хватит смежной виртуальной памяти, не удастся прочитать из каналов или других файлов, и т. Д.).

Вот решение, которое считывает 128 тыс. Блоков одновременно, и до тех пор, пока ваше регулярное выражение соответствует строке меньше этого размера, это будет работать. Также обратите внимание, что вы не ограничены использованием регулярных выражений в одну строку. Это решение работает достаточно быстро, хотя я подозреваю, что оно будет немного медленнее, чем использование mmap. Вероятно, это зависит больше от того, что вы делаете с совпадениями, а также от размера / сложности регулярного выражения, которое вы ищете.

Метод обеспечит сохранение в памяти максимум 2 блоков. Возможно, вы захотите применить по крайней мере 1 совпадение на блок в качестве проверки работоспособности в некоторых случаях использования, но этот метод будет усечен, чтобы сохранить максимум 2 блока в памяти. Это также гарантирует, что любое совпадение с регулярным выражением, которое съедает до конца текущего блока, НЕ дается, и вместо этого сохраняется последняя позиция для того момента, когда истощен истинный ввод или у нас есть другой блок, которому регулярное выражение соответствует до конца, в чтобы лучше соответствовать шаблонам типа "[^\n]+" или "xxx$". Вы все еще можете разбить вещи, если у вас есть заглядывание в конец регулярного выражения, например, xx(?! Xyz), где yz находится в следующем блоке, но в большинстве случаев вы можете обойти использование таких шаблонов.

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

Чтобы проверить / исследовать, вы можете запустить это:

# 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'))

Вот вывод:

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"

Это может быть полезно в сочетании с быстрым анализом большого XML, где он может быть разбит на мини-DOM на основе подэлемента от имени root, вместо того, чтобы погружаться в обработку обратных вызовов и состояний при использовании парсера SAX. Это также позволяет вам быстрее фильтровать XML. Но я использовал его и для других целей. Я немного удивлен, что подобные рецепты не так легко доступны в сети!

Еще одна вещь: синтаксический анализ в Юникоде должен работать, пока передаваемый поток генерирует строки Юникода, и если вы используете классы символов, такие как \ w, вам нужно добавить флаг re.U в re.compile. конструкция образца. В этом случае block_size фактически означает количество символов вместо количества байтов.

Другие вопросы по тегам