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