在没有time.sleep的python中尾部-f

时间:2009-09-25 07:45:08

标签: python

我需要在python中模拟“tail -f”,但我不想在读取循环中使用time.sleep。我想要一些更优雅的东西,如某种阻塞读取,或select.select超时,但python 2.6“选择”文档具体说:“它不能用于常规文件,以确定文件是否自上次读取后增长。 “ 还有其他方法吗? 如果没有给出解决方案,我会在几天内阅读尾部的C源代码以试图找出它。我希望他们不要睡觉,呵呵 感谢。

MarioR

10 个答案:

答案 0 :(得分:33)

(更新) 要么使用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

答案 1 :(得分:11)

为了最大限度地减少睡眠问题,我修改了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,

答案 2 :(得分:10)

从文件中读取时,您唯一的选择就是睡眠(see the source code)。如果您从管道读取,您可以简单地读取,因为读取将阻塞,直到准备好数据。

原因是操作系统不支持“等待某人写入文件”的概念。直到最近,一些文件系统添加了一个API,您可以在其中监听对文件所做的更改,但是尾部太旧而无法使用此API,并且它在任何地方都无法使用。

答案 3 :(得分:1)

Linux的C最简单的tail -f实现是这样的:

#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实现应该是什么样的。< / p>

这是一个正确的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适配器,例如inotifypyinotifypython-inotify。那些基本上可以完成INotify类的工作。

答案 4 :(得分:0)

IMO你应该使用sleep,它适用于所有平台,代码很简单

否则,您可以使用特定于平台的API,它可以在文件更改时告诉您 例如在窗口上使用文件夹FindFirstChangeNotification并观察FILE_NOTIFY_CHANGE_LAST_WRITE事件

在linux上我认为你可以使用i-notify

在Mac OSX上使用FSEvents

答案 5 :(得分:0)

您可以看到here如何使用inotify执行“tail -f”:

  

这是一个示例[原文如此],以显示如何使用inotify模块,它可能是   非常有用但不变。

     

Watcher实例允许您为发生的任何事件定义回调   在任何文件或目录和子目录上。

     

inotify模块来自Recipe 576375

答案 6 :(得分:0)

我见过的大多数实现都使用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

话虽如此,考虑到类似于libinotify的任何解决方案都存在可移植性问题,我可能会重新考虑使用readlines()/ sleep()。请参阅:http://code.activestate.com/recipes/577968-log-watcher-tail-f-log/

答案 7 :(得分:0)

有一个名为sh的真棒库可以使用线程块来拖尾文件。

DataOutputStream

答案 8 :(得分:-2)

为什么不在subprocess.call本身使用tail

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

编辑:已修复以消除额外的shell进程。

Edit2:已修复以消除已弃用的os.popen,因此需要插入参数,转义espace和其他内容,然后运行shell进程。

答案 9 :(得分:-2)

如果您可以在所有平台上使用GLib,则应使用glib.io_add_watch;那么你可以使用普通的GLib主循环并在事件发生时处理事件,而不会有任何轮询行为。

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