2009-09-25 2 views
25

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

MarioR

+0

В чем проблема со сном? –

+0

Кстати, если вы ищете кросс-платформенное решение и, следовательно, не используете 'popen (" tail -f '% s' "% filename)' , вы должны знать, что объекты File в Windows неприемлемы select.select –

+2

В источнике хвоста C используется функция sleep(). Строго говоря, вам не нужно * спать, но не использовать его будет привязывать ваш процессор на 100%. Почему бы не использовать простое решение? –

ответ

-2

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

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

Edit: Fixed устранить дополнительный процесс оболочки.

Edit2: Фиксированный направленный на устранение осуждается os.popen и, таким образом, необходимость интерполяции параметров, избежать ESPACES и другие вещи, а затем запустить процесс оболочки.

+10

нет хвоста на окнах –

+2

-1: popen - это неправильный путь - он вызывает новый процесс оболочки без необходимости просто запускать программу хвоста. – nosklo

+0

nosklo: Вы правы. Я исправил его для использования exec. Таким образом, вы получаете преимущество оболочки для синтаксического анализа командной строки, но не имеете накладных расходов на дополнительный процесс. –

10

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

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

32

(обновление) Либо использовать 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 
+0

Хех, я собирался опубликовать почти точно такой же код (хотя и не как генератор, который намного шире), +1 – dbr

+0

Почему бы не 'if line: yield line time.sleep (0.1)'? Это может вызвать дополнительные сон, но на самом деле это не очень важно (на мой взгляд). –

+1

, если это вызывает дополнительные сны, почему да? –

0

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

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

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

На Mac OSX использовать FSEvents

-2

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

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

+0

Это будет работать для чтения до конца файла один раз, но не для чтения дополнительных данных, как написано. – daf

0

Вы можете увидеть here, как сделать "хвост -f", как с помощью Inotify:

Это Exemple [так], чтобы показать, как использовать Inotify модуль, он может be очень полезно не изменилось, хотя.

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

Inotify модуль из рецепта 576375

+1

Ваш ответ должен содержать код. Нет никакой гарантии, что ссылка будет работать в будущем. –

0

Большинство реализаций я видел использование readlines()/спящий режим(). раствора на основе 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/

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, 
0

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

#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("") 

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

0

Существует потрясающая библиотека под названием sh, которая может зачерпнуть файл с блоком потоков.

for line in sh.tail('-f', '/you_file_path', _iter=True): 
    print(line) 
Смежные вопросы