Как реализовать питонический эквивалент tail -F? - PullRequest
24 голосов
/ 09 ноября 2009

Каков питонный способ наблюдения за хвостом растущего файла на наличие определенных ключевых слов?

В оболочке я могу сказать:

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

Ответы [ 10 ]

24 голосов
/ 09 ноября 2009

Ну, самый простой способ - постоянно читать из файла, проверять, что нового и проверять наличие хитов.

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)

Это решение с readline работает, если вы знаете, что ваши данные будут отображаться в строках.

Если данные представляют собой какой-то поток, вам нужен буфер, больший, чем самый большой word, который вы ищете, и заполните его первым. Так становится немного сложнее ...

5 голосов
/ 10 ноября 2009
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)

Я использую это для мониторинга файлов журнала. В полной реализации я храню list_of_matches в файле конфигурации, чтобы его можно было использовать для нескольких целей. В моем списке улучшений есть поддержка регулярных выражений вместо простого совпадения "in".

4 голосов
/ 16 марта 2015

вы можете использовать pytailf : простой хвост питона -f обертка

from tailf import tailf    

for line in tailf("myfile.log"):
    print line
4 голосов
/ 10 ноября 2009

Вы можете использовать select для опроса нового содержимого в файле.

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)
2 голосов
/ 11 сентября 2013

Похоже, для этого есть пакет: https://github.com/kasun/python-tail

2 голосов
/ 09 ноября 2009

РЕДАКТИРОВАТЬ: как отмечено в комментарии ниже, O_NONBLOCK не работает для файлов на диске. Это по-прежнему поможет, если кто-то еще придет посмотреть на хвостовые данные, поступающие из сокета или именованного канала или другого процесса, но он не отвечает на фактический вопрос, который был задан . Оригинальный ответ остается ниже для потомков. (Вызов «tail» и «grep» сработает, но в любом случае это не ответ.)

Либо откройте файл с помощью O_NONBLOCK и используйте select для опроса доступности для чтения, а затем read для чтения новых данных и строковых методов для фильтрации строк в конце файла ... или просто используйте модуль subprocess и пусть tail и grep выполняют работу за вас так же, как вы это делаете в оболочке.

1 голос
/ 28 июня 2017

Если вам просто нужно мертвое простое решение на Python 3 для обработки строк текстового файла в том виде, в котором они написаны, и вам не нужна поддержка Windows, для меня это сработало:

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

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

1 голос
/ 09 ноября 2009

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

Это должно работать:

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

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

0 голосов
/ 10 ноября 2009

Вы можете использовать collections.deque для реализации tail.

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

Конечно, это читает все содержимое файла, но это аккуратный и лаконичный способ реализации tail.

0 голосов
/ 09 ноября 2009

Насколько мне известно, в списке функций Python нет эквивалента "tail". Решением было бы использовать tell () (получить размер файла) и read () для определения конечных строк.

Эта запись в блоге (не мной) имеет записанную функцию, выглядит подходящей для меня! http://www.manugarg.com/2007/04/real-tailing-in-python.html

...