2009-09-25 14 views
25

Muszę emulować "ogon -f" w python, ale nie chcę używać time.sleep w pętli czytania. Chcę czegoś bardziej eleganckiego, jak coś w rodzaju blokowania odczytu lub select.select z limitem czasu, ale w python 2.6 "wybierz" dokumentacja wyraźnie mówi: "nie można jej używać w zwykłych plikach do określenia, czy plik urósł od czasu ostatniego odczytu. " W inny sposób? Za kilka dni, jeśli nie podam żadnego rozwiązania, przeczytam kod źródłowy C, aby spróbować go znaleźć. Mam nadzieję, że nie śpią, hehe Dzięki.ogon -f w python bez czasu.sleep

MarioR

+0

na czym polega problem ze snem? –

+0

btw, jeśli szukasz rozwiązania wieloplatformowego, a tym samym nie używasz 'popen (" tail -f '% s' ""% nazwy pliku ") powinieneś wiedzieć, że obiekty plików w systemie Windows nie są akceptowane przez select.select –

+2

Źródło ogona C używa sleep(). Ściśle mówiąc, nie * potrzebujesz * snu, ale nie używając go, ustawi twój procesor na 100%. Dlaczego nie skorzystać z łatwego rozwiązania? –

Odpowiedz

-2

Dlaczego nie można po prostu użyć subprocess.call na samej tail?

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

Edit: Poprawiono wyeliminować dodatkowy proces powłoki.

Edit2: stałej Aby wyeliminować przestarzałe os.popen a tym samym konieczność interpolacji parametrów ucieczki ESPACES oraz inny materiał, a następnie uruchomić proces powłoki.

+10

brak ogona w oknach –

+2

-1: popen jest w niewłaściwy sposób - powoduje niepotrzebny nowy proces powłoki, aby uruchomić program ogonowy. – nosklo

+0

nosklo: Masz rację. Naprawiłem to, aby użyć exec. W ten sposób uzyskuje się korzyści powłoki dla parsowania wiersza poleceń, ale nie ma narzutu dodatkowego procesu. –

10

Podczas czytania z pliku, jedynym wyjściem jest sen (see the source code). Jeśli czytasz z potoku, możesz po prostu przeczytać, ponieważ odczyt zostanie zablokowany, dopóki nie będą gotowe dane.

Powodem jest to, że system operacyjny nie obsługuje pojęcia "czekać na kogoś, kto napisze do pliku". Dopiero niedawno niektóre systemy plików dodały interfejs API, w którym można odsłuchiwać zmiany wprowadzone w pliku, ale ogon jest zbyt stary, aby korzystać z tego interfejsu API, a także nie jest dostępny wszędzie.

32

(aktualizacja) Albo użyć FS monitoruje narzędzi

lub jednorazowe uśpienia (który chciałbym uważasz za dużo bardziej elegancki).

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

Heh, miałem zamiar opublikować prawie dokładnie ten sam kod (choć nie jako generator, który jest o wiele bardziej elegancki), +1 – dbr

+0

Dlaczego nie 'if line: yield line time.leep (0.1)'? Może to powodować dodatkowe snu, ale to naprawdę nie jest wielka sprawa (moim zdaniem). –

+1

jeśli powoduje dodatkowe snu, dlaczego tak? –

0

IMO należy użyć sen, to działa na wszystkich platform i kod będzie prosty

W przeciwnym wypadku można użyć konkretnych platform API, które można powiedzieć, kiedy zmiany pliku np na użyciu okna FindFirstChangeNotification na folderze i obserwować wydarzenia FILE_NOTIFY_CHANGE_LAST_WRITE

Na linux myślę, że można użyć i-notify

W systemie Mac OS X wykorzystać FSEvents

-2

Jeśli można użyć GLib na wszystkich platformach, należy użyć glib.io_add_watch; wtedy możesz użyć zwykłego mainstreamu GLib i przetwarzać zdarzenia w miarę ich pojawiania się, bez żadnego zachowania ankietowego.

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

+0

To będzie działać do odczytu do końca pliku tylko raz, ale nie do odczytu dodatkowych danych w trakcie ich zapisywania. – daf

0

Widać here jak to zrobić "tail -f" jak za pomocą inotify:

To exemple [sic!], Aby pokazać, w jaki sposób korzystać z modułu inotify, może być jednak bardzo niezmieniony niezmieniony.

Instancja kontrolera umożliwia zdefiniowanie wywołań zwrotnych dla każdego zdarzenia, które wystąpi na dowolnym pliku lub katalogu i podkatalogach.

Moduł inotify wynosi od Receptura 576375

+1

Twoja odpowiedź powinna zawierać kod. Nie ma gwarancji, że link będzie nadal działał w przyszłości. –

0

Większość implementacji widziałem użytku readlines()/sleep(). rozwiązanie oparte na inotify lub podobnym potędze być szybciej, ale rozważ to:

  • raz libinotify powie Ci plik został zmieniony by skończyć się za pomocą readlines() i tak
  • nazywając readlines() przeciw plik, który się nie zmienił, co w efekcie skończy się bez libinotify, jest już dość szybką operacją:

    giampaolo @ ubuntu: ~ $ python -m timeit -s "f = open ('foo.py ',' r '); f.read() "-c" f.readlines() " 1000000 pętli, najlepiej z 3: 0,41 usec na pętlę

Powiedziawszy to, biorąc pod uwagę, że każde rozwiązanie podobne do libinotify ma problemy z przenośnością, mogę ponownie rozważyć użycie readlines()/sleep(). Zobacz: http://code.activestate.com/recipes/577968-log-watcher-tail-f-log/

11

Aby zminimalizować problemy ze snem, zmodyfikowałem rozwiązanie Tzury Bar Yochay, a teraz sprawdza ono szybko, jeśli jest aktywność, a po kilku sekundach braku aktywności analizuje tylko co sekundę.

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

Najprostszym C realizacja tail -f for Linux to:

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

To jest tylko minimalny przykład to oczywiście brakuje sprawdzanie błędów i nie zauważy, kiedy plik został usunięty/przeniesiony, ale powinien dać dobre pojęcie o tym, jak powinna wyglądać implementacja Pythona.

Oto odpowiednia implementacja Pythona, która używa wbudowanego ctypes do rozmowy z inotify w sposób opisany powyżej.

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

Należy zauważyć, że istnieją różne inotify adaptery do Python, takie jak inotify, pyinotify i python-inotify. Te w zasadzie wykonują pracę klasy INotify.

0

Istnieje niesamowita biblioteka o nazwie sh może kończyć plik z blokiem wątków.

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