Подтвердить что ты не робот

Tail -f в python без time.sleep

Мне нужно эмулировать "tail -f" в python, но я не хочу использовать time.sleep в цикле чтения. Я хочу, чтобы что-то более элегантное, вроде какой-то блокировки, или select.select с тайм-аутом, но в python 2.6 "select" документация специально говорит: "его нельзя использовать в обычных файлах, чтобы определить, вырос ли файл с момента последнего чтения." Любым другим путем? Через несколько дней, если не будет дано решение, я прочитаю исходный код хвоста C, чтобы попытаться понять это. Надеюсь, они не используют сон, хе-хе Спасибо.

MarioR

4b9b3361

Ответ 1

(обновление) Либо используйте инструменты FS-мониторов

Или одно использование сна (которое я бы счел более элегантным).

import time
def follow(thefile):
    thefile.seek(0,2)      # Go to the end of the file
    while True:
         line = thefile.readline()
         if not line:
             time.sleep(0.1)    # Sleep briefly
             continue
         yield line

logfile = open("access-log")
loglines = follow(logfile)
for line in loglines:
    print line

Ответ 2

Чтобы свести к минимуму проблемы со сном, я изменил решение Tzury Bar Yochay, и теперь он быстро опрометчиво, если есть активность, и через несколько секунд никакой активности он проверяет только каждую секунду.

import time

def follow(thefile):
    thefile.seek(0,2)      # Go to the end of the file
    sleep = 0.00001
    while True:
        line = thefile.readline()
        if not line:
            time.sleep(sleep)    # Sleep briefly
            if sleep < 1.0:
                sleep += 0.00001
            continue
        sleep = 0.00001
        yield line

logfile = open("/var/log/system.log")
loglines = follow(logfile)
for line in loglines:
    print line,

Ответ 3

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

Причина этого в том, что ОС не поддерживает понятие "ждать, пока кто-то напишет файл". Совсем недавно некоторые файловые системы добавили API, в котором вы можете прослушивать изменения, внесенные в файл, но хвост слишком стар, чтобы использовать этот API, и он также недоступен везде.

Ответ 4

ИМО вы должны использовать сон, он работает на всей платформе, а код будет прост

В противном случае вы можете использовать специфичные для платформы API-интерфейсы, которые могут сообщать вам при смене файла например в окне используйте FindFirstChangeNotification в папке и посмотрите события FILE_NOTIFY_CHANGE_LAST_WRITE

В linux я думаю, вы можете использовать i-notify

В Mac OSX используйте FSEvents

Ответ 5

Вы можете увидеть здесь, как сделать "хвост -f", например, используя inotify:

Это пример [sic], чтобы показать, как использовать модуль inotify, это может быть очень полезно, но не изменилось.

Экземпляр Watcher позволяет определять обратные вызовы для любого события, которое происходит на любой файл или каталог и подкаталоги.

Модуль inotify из рецепта 576375

Ответ 6

Большинство реализаций, которые я видел, используют readlines()/sleep(). Решение, основанное на inotify или подобном, может быть быстрее, но рассмотрите это:

  • Как только libinotify сообщит вам, что файл был изменен, вы в конечном итоге используете readlines() в любом случае
  • вызов readlines() в отношении файла, который не изменился, что вы делаете без libinotify, уже довольно быстрая операция:

    giampaolo @ubuntu: ~ $python -m timeit -s "f = open ('foo.py', 'r'); f.read()" -c "f.readlines()" 1000000 циклов, лучше всего 3: 0.41 usec за цикл

Сказав это, учитывая, что любое решение, подобное libinotify, имеет проблемы с переносимостью, я могу пересмотреть использование readlines()/sleep(). См.: http://code.activestate.com/recipes/577968-log-watcher-tail-f-log/

Ответ 7

Простейшая реализация tail -f для Linux для C такова:

#include <unistd.h>
#include <sys/inotify.h>

int main() {
    int inotify_fd = inotify_init();
    inotify_add_watch(inotify_fd, "/tmp/f", IN_MODIFY);
    struct inotify_event event;
    while (1) {
        read(inotify_fd, &event, sizeof(event));
        [file has changed; open, stat, read new data]
    }
}

Это всего лишь минимальный пример, который, очевидно, не имеет проверки ошибок и не заметит, когда файл будет удален/перемещен, но он должен дать хорошее представление о том, как должна выглядеть реализация Python.

Вот правильная реализация Python, которая использует встроенный ctypes для связи с inotify описанным выше способом.

""" simple python implementation of tail -f, utilizing inotify. """

import ctypes
from errno import errorcode
import os
from struct import Struct

# constants from <sys/inotify.h>
IN_MODIFY = 2
IN_DELETE_SELF = 1024
IN_MOVE_SELF = 2048

def follow(filename, blocksize=8192):
    """
    Monitors the file, and yields bytes objects.

    Terminates when the file is deleted or moved.
    """
    with INotify() as inotify:
        # return when we encounter one of these events.
        stop_mask = IN_DELETE_SELF | IN_MOVE_SELF

        inotify.add_watch(filename, IN_MODIFY | stop_mask)

        # we have returned this many bytes from the file.
        filepos = 0
        while True:
            with open(filename, "rb") as fileobj:
                fileobj.seek(filepos)
                while True:
                    data = fileobj.read(blocksize)
                    if not data:
                        break
                    filepos += len(data)
                    yield data

            # wait for next inotify event
            _, mask, _, _ = inotify.next_event()
            if mask & stop_mask:
                break

LIBC = ctypes.CDLL("libc.so.6")


class INotify:
    """ Ultra-lightweight inotify class. """
    def __init__(self):
        self.fd = LIBC.inotify_init()
        if self.fd < 0:
            raise OSError("could not init inotify: " + errorcode[-self.fd])
        self.event_struct = Struct("iIII")

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc, exc_tb):
        self.close()

    def close(self):
        """ Frees the associated resources. """
        os.close(self.fd)

    def next_event(self):
        """
        Waits for the next event, and returns a tuple of
        watch id, mask, cookie, name (bytes).
        """
        raw = os.read(self.fd, self.event_struct.size)
        watch_id, mask, cookie, name_size = self.event_struct.unpack(raw)
        if name_size:
            name = os.read(self.fd, name_size)
        else:
            name = b""

        return watch_id, mask, cookie, name

    def add_watch(self, filename, mask):
        """
        Adds a watch for filename, with the given mask.
        Returns the watch id.
        """
        if not isinstance(filename, bytes):
            raise TypeError("filename must be bytes")
        watch_id = LIBC.inotify_add_watch(self.fd, filename, mask)
        if watch_id < 0:
            raise OSError("could not add watch: " + errorcode[-watch_id])
        return watch_id


def main():
    """ CLI """
    from argparse import ArgumentParser
    cli = ArgumentParser()
    cli.add_argument("filename")
    args = cli.parse_args()
    import sys
    for data in follow(args.filename.encode()):
        sys.stdout.buffer.write(data)
        sys.stdout.buffer.flush()

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print("")

Обратите внимание, что для Python существуют различные адаптеры inotify, такие как inotify, pyinotify и python-inotify. В основном это будет выполнять класс inotify.

Ответ 8

Там awesome библиотека под названием sh может закрепить файл с блоком потока.

for line in sh.tail('-f', '/you_file_path', _iter=True):
    print(line)

Ответ 9

Почему бы вам просто не использовать subprocess.call в tail?

subproces.call(['tail', '-f', filename])

Изменить: Исправлено устранение дополнительного процесса оболочки.

Edit2: Исправлено устранение устаревших os.popen и, следовательно, необходимость интерполирования параметров, эвакуации и других материалов, а затем запуск процесса оболочки.

Ответ 10

Если вы можете использовать GLib на всех платформах, вы должны использовать glib.io_add_watch; то вы можете использовать обычный GLL mainloop и обрабатывать события по мере их возникновения, без какого-либо опроса.

http://library.gnome.org/devel/pygobject/stable/glib-functions.html#function-glib--io-add-watch