2009-11-09 5 views
20

¿Cuál es la forma pitónica de ver el final de un archivo en crecimiento por la aparición de ciertas palabras clave?¿Cómo implementar un equivalente pitónico de cola -F?

Con cáscara podría decir:

tail -f "$file" | grep "$string" | while read hit; do 
    #stuff 
done 
+0

Algo similar: http://stackoverflow.com/questions/136168/tail-a-file-with-python – Mark

+2

Esas dos preguntas parecen idénticas, pero esta se trata de supervisar constantemente un archivo para nuevas líneas, mientras que el otro La pregunta se trata de leer las últimas líneas x – dbr

Respuesta

17

Bueno, la forma más sencilla sería leer constantemente desde el archivo, comprobar lo que es nuevo y prueba de golpes.

import time 

def watch(fn, words): 
    fp = open(fn, 'r') 
    while True: 
     new = fp.readline() 
     # Once all lines are read this just returns '' 
     # until the file changes and a new line appears 

     if new: 
      for word in words: 
       if word in new: 
        yield (word, new) 
     else: 
      time.sleep(0.5) 

fn = 'test.py' 
words = ['word'] 
for hit_word, hit_sentence in watch(fn, words): 
    print "Found %r in line: %r" % (hit_word, hit_sentence) 

Esta solución con readline obras si conoce sus datos aparecerán en las líneas.

Si los datos son algún tipo de flujo, necesita un búfer, más grande que el word más grande que está buscando, y llénelo primero. Se vuelve un poco más complicado de esa manera ...

+1

Estoy un poco esbozado al abrir el archivo varias veces. Puede usar os.stat para indicarle si el archivo se ha modificado. –

+9

Mira de nuevo; abrir solo se llama una vez. readline se llama repetidamente. –

0

Que yo sepa, no hay equivalente a "cola" en la lista de funciones de Python. La solución sería usar tell() (obtener tamaño de archivo) y read() para calcular las líneas finales.

¡Esta publicación del blog (no por mí) tiene la función escrita, me parece apropiada! http://www.manugarg.com/2007/04/real-tailing-in-python.html

1

Si no puede limitar el problema para trabajar con una lectura basada en línea, necesita recurrir a bloques.

Esto debería funcionar:

import sys 

needle = "needle" 

blocks = [] 

inf = sys.stdin 

if len(sys.argv) == 2: 
    inf = open(sys.argv[1]) 

while True: 
    block = inf.read() 
    blocks.append(block) 
    if len(blocks) >= 2: 
     data = "".join((blocks[-2], blocks[-1])) 
    else: 
     data = blocks[-1] 

    # attention, this needs to be changed if you are interested 
    # in *all* matches separately, not if there was any match ata all 
    if needle in data: 
     print "found" 
     blocks = [] 
    blocks[:-2] = [] 

    if block == "": 
     break 

El desafío consiste en garantizar que hacer coincidir la aguja incluso si está separada por dos bloques-límites.

+0

Buena captura; Nadando en archivos de registro todo el día, a veces olvido que no todos los datos vienen en líneas. – pra

2

EDITAR: como se indica en el comentario a continuación, O_NONBLOCK no funciona para los archivos en el disco. Esto todavía ayudará si alguien más busca datos de la cola procedentes de un socket o canalización con nombre u otro proceso, pero no responde la pregunta real que se le formuló al. La respuesta original permanece debajo para la posteridad. (Llamando a la cola y grep va a funcionar, pero es un no-respuesta de las clases de todos modos.)

abrir el archivo con O_NONBLOCK y utilizar select para sondear la disponibilidad de lectura y luego read para leer los nuevos datos y la cadena de métodos para filtrar líneas al final de un archivo ... o simplemente use el módulo subprocess y deje que tail y grep hagan el trabajo por usted como lo haría en el shell.

+1

IO no bloqueante no es compatible con archivos en la mayoría (si no en todos) del sistema operativo. Eso incluye Linux y FreeBSD. Si lo fuera, la combinación de E/S no bloqueante y de sondeo/selección/lo que sea sería lo mismo que bloquear la lectura, lo que no hace lo que necesita el OP. – WGH

+0

@WGH: ¡Buen punto! Creo que me enteré de que después de haber escrito esto, pero es importante tener en cuenta a cualquiera que se acerque a esta pregunta más adelante, por lo que he precedido mi respuesta con un descargo de responsabilidad en ese sentido. –

0

Puede usar collections.deque para implementar la cola.

De http://docs.python.org/library/collections.html#deque-recipes ...

def tail(filename, n=10): 
    'Return the last n lines of a file' 
    return deque(open(filename), n) 

Por supuesto, esto se lee el contenido del archivo entero, pero es una manera ordenada y concisa de la aplicación de la cola.

+3

Eso responde una pregunta diferente. – WGH

5
def tail(f): 
    f.seek(0, 2) 

    while True: 
     line = f.readline() 

     if not line: 
      time.sleep(0.1) 
      continue 

     yield line 

def process_matches(matchtext): 
    while True: 
     line = (yield) 
     if matchtext in line: 
      do_something_useful() # email alert, etc. 


list_of_matches = ['ERROR', 'CRITICAL'] 
matches = [process_matches(string_match) for string_match in list_of_matches]  

for m in matches: # prime matches 
    m.next() 

while True: 
    auditlog = tail(open(log_file_to_monitor)) 
    for line in auditlog: 
     for m in matches: 
      m.send(line) 

Utilizo esto para controlar los archivos de registro. En la implementación completa, guardo list_of_matches en un archivo de configuración para que pueda usarse con múltiples propósitos. En mi lista de mejoras es compatible con expresiones regulares en lugar de una simple coincidencia 'in'.

+0

Ese es un uso extraño/interesante de generadores. – dbr

3

Puede usar seleccionar para sondear los nuevos contenidos en un archivo.

def tail(filename, bufsize = 1024): 
    fds = [ os.open(filename, os.O_RDONLY) ] 
    while True: 
     reads, _, _ = select.select(fds, [], []) 
     if 0 < len(reads): 
      yield os.read(reads[0], bufsize) 
+4

No puede usar seleccionar para sondear archivos para nuevos datos, solo conectores. Ver: http://docs.python.org/library/select.html, primer párrafo, última oración: no se puede usar en archivos regulares para determinar si un archivo ha crecido desde la última vez que se leyó. – synthesizerpatel

5

puede utilizar pytailf: Simple pitón cola envoltorio -f

from tailf import tailf  

for line in tailf("myfile.log"): 
    print line 
+0

Esto no funcionará para Python 3. – James

+0

No pierdas tu tiempo con tailf, se llama "/ usr/bin/tail" internamente, que seguramente no es lo que quieres. –

1

Si sólo necesita una solución simple muerto Python 3 para el procesamiento las líneas de un archivo de texto tal como están escritas, y usted no necesita soporte de Windows, esto funcionó bien para mí:

import subprocess 
def tailf(filename): 
    #returns lines from a file, starting from the beginning 
    command = "tail -n +1 -F " + filename 
    p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, universal_newlines=True) 
    for line in p.stdout: 
     yield line 
for line in tailf("logfile"): 
    #do stuff 

Bloquea la espera de que se escriban nuevas líneas, por lo que no es adecuado para uso asincrónico sin algunas modificaciones.

Cuestiones relacionadas