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 фактически означает количество символов вместо количества байтов.