我需要在python中模拟“tail -f”,但我不想在读取循环中使用time.sleep。我想要一些更优雅的东西,如某种阻塞读取,或select.select超时,但python 2.6“选择”文档具体说:“它不能用于常规文件,以确定文件是否自上次读取后增长。 “ 还有其他方法吗? 如果没有给出解决方案,我会在几天内阅读尾部的C源代码以试图找出它。我希望他们不要睡觉,呵呵 感谢。
MarioR
答案 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
适配器,例如inotify
,pyinotify
和python-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或类似的解决方案可能更快但考虑到这一点:
对一个没有改变的文件调用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