tail -f 在 python 中没有 time.sleep

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1475950/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 22:21:35  来源:igfitidea点击:

tail -f in python with no time.sleep

python

提问by Tzury Bar Yochay

I need to emulate "tail -f" in python, but I don't want to use time.sleep in the reading loop. I want something more elegant like some kind of blocking read, or select.select with timeout, but python 2.6 "select" documentation specifically says: "it cannot be used on regular files to determine whether a file has grown since it was last read." Any other way? In a few days if no solution is given I will read tail's C source code to try to figure it out. I hope they don't use sleep, hehe Thanks.

我需要在 python 中模拟“tail -f”,但我不想在阅读循环中使用 time.sleep。我想要一些更优雅的东西,比如某种阻塞读取,或者带有超时的 select.select,但是 python 2.6“select”文档特别指出:“它不能用于常规文件来确定自上次读取以来文件是否已经增长。 ” 还有什么办法吗?几天后,如果没有给出解决方案,我将阅读tail的C源代码以试图弄清楚。我希望他们不要使用睡眠,呵呵,谢谢。

MarioR

马里奥

回答by Tzury Bar Yochay

(update) Either use FS monitors tools

(更新)要么使用 FS 监视器工具

Or a single sleep usage (which I would you consider as much more elegant).

或者单次睡眠使用(我认为这更优雅)。

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

回答by James Reynolds

To minimize the sleep issues I modified Tzury Bar Yochay's solution and now it polls quickly if there is activity and after a few seconds of no activity it only polls every second.

为了尽量减少睡眠问题,我修改了 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,

回答by Aaron Digulla

When reading from a file, your only choice is sleep (see the source code). If you read from a pipe, you can simply read since the read will block until there is data ready.

从文件中读取时,您唯一的选择是睡眠(请参阅源代码)。如果您从管道读取,您可以简单地读取,因为读取将阻塞,直到有数据准备好。

The reason for this is that the OS doesn't support the notion "wait for someone to write to a file". Only recently, some filesystems added an API where you can listen for changes made to a file but tail is too old to use this API and it's also not available everywhere.

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

回答by mic_e

The simplest Cimplementation of tail -ffor Linux is this:

最简单Ctail -fLinux实现是这样的:

#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]
    }
}

This is just a minimal example that's obviously lacking error checking and won't notice when the file is deleted/moved, but it should give a good idea about what the Python implementation should look like.

这只是一个最小的例子,显然缺乏错误检查,并且不会注意到文件何时被删除/移动,但它应该很好地了解 Python 实现应该是什么样子。

Here's a proper Python implementation that uses the built-in ctypesto talk to inotifyin the way outlined above.

这是一个正确的 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("")

Note that there are various inotifyadapters for Python, such as inotify, pyinotifyand python-inotify. Those would basically do the work of the INotifyclass.

请注意,inotifyPython有多种适配器,例如inotifypyinotifypython-inotify。那些基本上会完成INotify班级的工作。

回答by Kixoms

There's an awesome library called shcan tail a file with thread block.

有一个很棒的库叫做sh可以用线程块来拖尾文件。

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

回答by dugres

You can see herehow to do a "tail -f" like using inotify:

你可以在这里看到如何像使用 inotify 一样执行“tail -f”:

This is an exemple[sic] to show how to use the inotify module, it could be very usefull unchanged though.

A Watcher instance let you define callbacks for any event that occur on any file or directory and subdirectories.

The inotify module is from Recipe 576375

这是一个展示如何使用 inotify 模块的示例[原文如此],但它可能非常有用,但无需更改。

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

inotify 模块来自配方 576375

回答by Giampaolo Rodolà

Most implementations I've seen use readlines() / sleep(). A solution based on inotify or similar mightbe faster but consider this:

我见过的大多数实现都使用 readlines() / sleep()。基于 inotify 或类似的解决方案可能会更快,但请考虑这一点:

  • once libinotify tells you a file has changed you would end up using readlines() anyway
  • calling readlines() against a file which hasn't changed, which is what you would end up doing without libinotify, is already a pretty fast operation:

    giampaolo@ubuntu:~$ python -m timeit -s "f = open('foo.py', 'r'); f.read()" -c "f.readlines()" 1000000 loops, best of 3: 0.41 usec per loop

  • 一旦 libinotify 告诉您文件已更改,无论如何您最终都会使用 readlines()
  • 对未更改的文件调用 readlines() 已经是一个非常快的操作,如果没有 libinotify,您最终会这样做:

    giampaolo@ubuntu:~$ python -m timeit -s "f = open('foo.py', 'r'); f.read()" -c "f.readlines()" 1000000 个循环,最好的 3 个:每个循环 0.41 微秒

Having said this, considering that any solution similar to libinotify has portability issues, I might reconsider using readlines() / sleep(). See: http://code.activestate.com/recipes/577968-log-watcher-tail-f-log/

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

回答by Anurag Uniyal

IMO you should use sleep, it works on all platform and code will be simple

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

Otherwise you can use platform specific APIs which can tell you when file change e.g. on window use FindFirstChangeNotificationon folder and watch for FILE_NOTIFY_CHANGE_LAST_WRITE events

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

On linux i think you can use i-notify

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

On Mac OSX use FSEvents

在 Mac OSX 上使用FSEvents

回答by u0b34a0f6ae

If you can use GLib on all platforms, you should use glib.io_add_watch; then you can use a normal GLib mainloop and process events as they happen, without any polling behavior.

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

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

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

回答by alex tingle

Why don't you just use subprocess.callon tailitself?

为什么不直接用subprocess.calltail自己身上?

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

Edit:Fixed to eliminate extra shell process.

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

Edit2:Fixed to eliminate deprecated os.popenand thus the need to interpolate parameters, escape espaces and other stuff, and then run a shell process.

Edit2:已修复以消除不推荐使用的情况os.popen,因此需要插入参数、转义空格和其他内容,然后运行 ​​shell 进程。