2011-02-14 19 views
13

tiene:Python: encontrar expresión regular en un archivo

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

Necesidad:
encontrar la posición (inicio y final) de una primera expresión regular coincidente en un archivo grande?
(a partir de current_pos=...)

¿Cómo puedo hacer esto?


Quiero tener esta función:

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] 

archivo se espera 'f' a ser grande.

+0

¿Podría mostrar un ejemplo más completo de lo que quiere hacer? Con algunas entradas y salidas de muestra junto con él. –

Respuesta

31

Una forma de buscar a través de archivos grandes es utilizar la biblioteca mmap para asignar el archivo a una gran porción de memoria. Luego puede buscar a través de él sin tener que leerlo explícitamente.

Por ejemplo, algo como:

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) 

Esto funciona bien para archivos muy grandes (lo he hecho para un archivo de 30 GB de tamaño, pero necesitará un sistema operativo de 64 bits si su archivo es más de un GB o dos).

+0

se ve genial, lo comprobaré tan pronto como pueda – Sergey

+0

¿Qué ocurre con la búsqueda de datos procedentes de socket? – socketpair

+0

Si usa un parámetro de tamaño de '0', usará todo el archivo. – mbomb007

3

El siguiente código funciona razonablemente bien con archivos de prueba de 2GB de tamaño.

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

Tenga en cuenta que la expresión regular no debe abarcar varias líneas.

0

NOTA: esto ha sido probado en python2.7. Puede que tenga que ajustar cosas en python 3 para manejar cadenas en vez de bytes, pero no debería ser demasiado doloroso con suerte.

Los archivos de memoria mapeados pueden no ser ideales para su situación (el modo de 32 bits aumenta la probabilidad de que no haya suficiente memoria virtual contigua, no pueda leerse desde tuberías u otros archivos que no sean archivos, etc.).

Aquí hay una solución que lee 128k bloques a la vez y siempre que su expresión regular coincida con una cadena más pequeña que ese tamaño, esto funcionará. También tenga en cuenta que no está restringido mediante el uso de expresiones regulares unifilares. Esta solución funciona bastante rápido, aunque sospecho que será un poco más lenta que usar mmap. Probablemente dependa más de lo que esté haciendo con las coincidencias, así como del tamaño/complejidad de la expresión regular que está buscando.

El método se asegurará de mantener un máximo de 2 bloques en la memoria. Es posible que desee aplicar al menos 1 partido por bloque como una comprobación de cordura en algunos casos de uso, pero este método se truncará para mantener el máximo de 2 bloques en la memoria. También se asegura de que NO se produzca ninguna coincidencia de expresiones regulares que consuma hasta el final del bloque actual y, en su lugar, se guarda la última posición para cuando la entrada verdadera se agote o tengamos otro bloque que corresponda a la expresión regular antes del final de, en para que coincida mejor con patrones como "[^ \ n] +" o "xxx $". Es posible que aún pueda romper cosas si tiene un vistazo al final de la expresión regular como xx (?! Xyz) donde yz está en el bloque siguiente, pero en la mayoría de los casos puede evitar el uso de dichos patrones.

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 

Para probar/explorar, puede ejecutar esto:

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

Aquí está la salida:

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" 

Esto puede ser útil en conjunción con rápido análisis de un gran XML donde se puede dividir en mini-DOMs basados ​​en un elemento secundario como root, en lugar de tener que sumergirse en el manejo de devoluciones de llamadas y estados cuando se usa un analizador SAX. También le permite filtrar a través de XML más rápido también. Pero también lo he usado para miles de otros propósitos. ¡Estoy sorprendido de que recetas como esta no estén más disponibles en la red!

Una cosa más: El análisis en Unicode debe funcionar siempre que la transmisión aprobada produzca cadenas Unicode, y si está utilizando las clases de caracteres como \ w, deberá agregar el indicador re.U a la construcción del patrón re.compile. En este caso, block_size realmente significa recuento de caracteres en lugar de recuento de bytes.

Cuestiones relacionadas