Pergunta

Eu tenho um arquivo de log que está sendo escrito por um outro processo que eu quero ver para mudanças. Cada vez que ocorre uma alteração que eu gostaria de ler os novos dados em fazer algum processamento sobre ele.

Qual é a melhor maneira de fazer isso? Eu estava esperando que haveria algum tipo de gancho a partir da biblioteca PyWin32. Eu encontrei a função win32file.FindNextChangeNotification mas não têm idéia de como pedir-lhe para assistir a um arquivo específico.

Se alguém tem feito nada parecido com isso eu ficaria muito grato de ouvir como ...

[Edit] eu deveria ter mencionado que eu estava depois uma solução que não requer votação.

[editar] Curses! Parece que este não funciona em uma unidade de rede mapeada. Estou janelas adivinhando não 'ouvir' todas as atualizações para o arquivo da maneira que faz em um disco local.

Foi útil?

Solução

Você já olhou para a documentação disponível sobre http://timgolden.me.uk /python/win32_how_do_i/watch_directory_for_changes.html ? Se você só precisa dele para funcionar no Windows 2ª exemplo parece ser exatamente o que você quer (se você trocar o caminho do diretório com a do arquivo que deseja assistir).

Caso contrário, a votação será, provavelmente, a opção só é realmente independente de plataforma.

Nota:. Eu não tentei qualquer uma destas soluções

Outras dicas

Você tentou usando Watchdog ?

Biblioteca Python API e shell utilitários para eventos do sistema de arquivos monitor.

Lista de monitoramento fácil com

  • A API de plataforma cruzada.
  • A ferramenta de shell para executar comandos em resposta a alterações de diretório.

Comece rapidamente com um exemplo simples em Quickstart ...

Se polling é bom o suficiente para você, eu tinha acabado de ver se o "tempo modificado" mudanças arquivo de stat. Para lê-lo:

os.stat(filename).st_mtime

(Observe também que a solução mudança evento nativo do Windows não funciona em todas as circunstâncias, por exemplo em unidades de rede.)

import os

class Monkey(object):
    def __init__(self):
        self._cached_stamp = 0
        self.filename = '/path/to/file'

    def ook(self):
        stamp = os.stat(self.filename).st_mtime
        if stamp != self._cached_stamp:
            self._cached_stamp = stamp
            # File has changed, so do something...

Se você quer uma solução multiplataforma, em seguida, verificar QFileSystemWatcher . Aqui um exemplo de código (não higienizado):

from PyQt4 import QtCore

@QtCore.pyqtSlot(str)
def directory_changed(path):
    print('Directory Changed!!!')

@QtCore.pyqtSlot(str)
def file_changed(path):
    print('File Changed!!!')

fs_watcher = QtCore.QFileSystemWatcher(['/path/to/files_1', '/path/to/files_2', '/path/to/files_3'])

fs_watcher.connect(fs_watcher, QtCore.SIGNAL('directoryChanged(QString)'), directory_changed)
fs_watcher.connect(fs_watcher, QtCore.SIGNAL('fileChanged(QString)'), file_changed)

Não deve funcionar em janelas (talvez com cygwin?), Mas para o usuário UNIX, você deve usar a chamada de sistema "fcntl". Aqui está um exemplo em Python. É basicamente o mesmo código, se você precisa escrevê-lo em C (nomes mesma função)

import time
import fcntl
import os
import signal

FNAME = "/HOME/TOTO/FILETOWATCH"

def handler(signum, frame):
    print "File %s modified" % (FNAME,)

signal.signal(signal.SIGIO, handler)
fd = os.open(FNAME,  os.O_RDONLY)
fcntl.fcntl(fd, fcntl.F_SETSIG, 0)
fcntl.fcntl(fd, fcntl.F_NOTIFY,
            fcntl.DN_MODIFY | fcntl.DN_CREATE | fcntl.DN_MULTISHOT)

while True:
    time.sleep(10000)

Confira pyinotify .

inotify substitui dnotify (a partir de uma resposta anterior) em Linuxes mais recentes e permite-nível de arquivo em vez de monitoramento em nível de diretório.

Bem, depois de um pouco de hacking do roteiro de Tim Golden, eu tenho o seguinte que parece funcionar muito bem:

import os

import win32file
import win32con

path_to_watch = "." # look at the current directory
file_to_watch = "test.txt" # look for changes to a file called test.txt

def ProcessNewData( newData ):
    print "Text added: %s"%newData

# Set up the bits we'll need for output
ACTIONS = {
  1 : "Created",
  2 : "Deleted",
  3 : "Updated",
  4 : "Renamed from something",
  5 : "Renamed to something"
}
FILE_LIST_DIRECTORY = 0x0001
hDir = win32file.CreateFile (
  path_to_watch,
  FILE_LIST_DIRECTORY,
  win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
  None,
  win32con.OPEN_EXISTING,
  win32con.FILE_FLAG_BACKUP_SEMANTICS,
  None
)

# Open the file we're interested in
a = open(file_to_watch, "r")

# Throw away any exising log data
a.read()

# Wait for new data and call ProcessNewData for each new chunk that's written
while 1:
  # Wait for a change to occur
  results = win32file.ReadDirectoryChangesW (
    hDir,
    1024,
    False,
    win32con.FILE_NOTIFY_CHANGE_LAST_WRITE,
    None,
    None
  )

  # For each change, check to see if it's updating the file we're interested in
  for action, file in results:
    full_filename = os.path.join (path_to_watch, file)
    #print file, ACTIONS.get (action, "Unknown")
    if file == file_to_watch:
        newText = a.read()
        if newText != "":
            ProcessNewData( newText )

Ele provavelmente poderia fazer com uma carga mais a verificação de erros, mas para simplesmente assistir a um arquivo de log e fazer algum processamento sobre ele antes de cuspi-la para a tela, isso funciona bem.

Obrigado a todos por sua entrada - grande material

Solução mais simples para mim está usando watchmedo ferramenta de vigilância

A partir https://pypi.python.org/pypi/watchdog agora tenho uma processo que olha para os arquivos SQL em um diretório e executa-los se necessário.

watchmedo shell-command \
--patterns="*.sql" \
--recursive \
--command='~/Desktop/load_files_into_mysql_database.sh' \
.

Verifique minha resposta a um pergunta semelhante. Você poderia tentar o mesmo loop em Python. Esta página sugere:

import time

while 1:
    where = file.tell()
    line = file.readline()
    if not line:
        time.sleep(1)
        file.seek(where)
    else:
        print line, # already has newline

Veja também a questão () um arquivo com Python .

Bem, desde que você está usando Python, você pode simplesmente abrir um arquivo e manter a leitura linhas a partir dele.

f = open('file.log')

Se a leitura linha é não vazias , você processá-lo.

line = f.readline()
if line:
    // Do what you want with the line

Você pode estar faltando que é ok para manter chamando readline no EOF. Ela só vai manter a retornar uma string vazia neste caso. E quando algo é anexado ao arquivo de log, a leitura vai continuar de onde parou, como você precisa.

Se você está procurando uma solução que usa eventos, ou uma biblioteca particular, por favor especifique isso na sua pergunta. Caso contrário, eu acho que esta solução é apenas multa.

Aqui está uma versão simplificada do código de Kender que aparece para fazer o mesmo truque e não importar todo o arquivo:

# Check file for new data.

import time

f = open(r'c:\temp\test.txt', 'r')

while True:

    line = f.readline()
    if not line:
        time.sleep(1)
        print 'Nothing New'
    else:
        print 'Call Function: ', line

Para assistir a um único arquivo com polling e dependências mínimas, aqui está um exemplo totalmente clarificado-out, com base na resposta de Deestan (acima):

import os
import sys 
import time

class Watcher(object):
    running = True
    refresh_delay_secs = 1

    # Constructor
    def __init__(self, watch_file, call_func_on_change=None, *args, **kwargs):
        self._cached_stamp = 0
        self.filename = watch_file
        self.call_func_on_change = call_func_on_change
        self.args = args
        self.kwargs = kwargs

    # Look for changes
    def look(self):
        stamp = os.stat(self.filename).st_mtime
        if stamp != self._cached_stamp:
            self._cached_stamp = stamp
            # File has changed, so do something...
            print('File changed')
            if self.call_func_on_change is not None:
                self.call_func_on_change(*self.args, **self.kwargs)

    # Keep watching in a loop        
    def watch(self):
        while self.running: 
            try: 
                # Look for changes
                time.sleep(self.refresh_delay_secs) 
                self.look() 
            except KeyboardInterrupt: 
                print('\nDone') 
                break 
            except FileNotFoundError:
                # Action on file not found
                pass
            except: 
                print('Unhandled error: %s' % sys.exc_info()[0])

# Call this function each time a change happens
def custom_action(text):
    print(text)

watch_file = 'my_file.txt'

# watcher = Watcher(watch_file)  # simple
watcher = Watcher(watch_file, custom_action, text='yes, changed')  # also call custom action function
watcher.watch()  # start the watch going

Como você pode ver na artigo de Tim Golden, apontado por Horst Gutmann , Win32 é relativamente complexo e relógios diretórios, não um único arquivo.

Eu gostaria de sugerir-lhe olhar para IronPython , que é uma .NET python implementação. Com IronPython você pode usar todo o .NET funcionalidade - incluindo

System.IO.FileSystemWatcher

que lida com arquivos individuais com um simples Event interface.

Esta é uma outra modificação do roteiro de Tim Goldan que roda em Linux e adiciona um observador simples para modificação do arquivo usando um dicionário (file => tempo).

uso: whateverName.py path_to_dir_to_watch

#!/usr/bin/env python

import os, sys, time

def files_to_timestamp(path):
    files = [os.path.join(path, f) for f in os.listdir(path)]
    return dict ([(f, os.path.getmtime(f)) for f in files])

if __name__ == "__main__":

    path_to_watch = sys.argv[1]
    print "Watching ", path_to_watch

    before = files_to_timestamp(path_to_watch)

    while 1:
        time.sleep (2)
        after = files_to_timestamp(path_to_watch)

        added = [f for f in after.keys() if not f in before.keys()]
        removed = [f for f in before.keys() if not f in after.keys()]
        modified = []

        for f in before.keys():
            if not f in removed:
                if os.path.getmtime(f) != before.get(f):
                    modified.append(f)

        if added: print "Added: ", ", ".join(added)
        if removed: print "Removed: ", ", ".join(removed)
        if modified: print "Modified ", ", ".join(modified)

        before = after

Este é um exemplo de busca de um arquivo para mudanças. Um que pode não ser a melhor maneira de fazê-lo, mas com certeza é uma forma curta.

ferramenta útil para reiniciar aplicação quando as mudanças foram feitas para a fonte. Eu fiz este quando se joga com pygame para que eu possa ver os efeitos ocorrem imediatamente após arquivo de save.

Quando usado em pygame verifique se as coisas no 'enquanto' laço é colocado em seu loop do jogo aka atualização ou qualquer outra coisa. Caso contrário, sua aplicação vai ficar preso em um loop infinito e você não vai ver a sua atualização jogo.

file_size_stored = os.stat('neuron.py').st_size

  while True:
    try:
      file_size_current = os.stat('neuron.py').st_size
      if file_size_stored != file_size_current:
        restart_program()
    except: 
      pass

No caso de você queria o código de reinício que eu encontrei na web. Aqui está. (Não é relevante para a questão, apesar de que poderia vir a calhar)

def restart_program(): #restart application
    python = sys.executable
    os.execl(python, python, * sys.argv)

Divirta-se fazendo elétrons fazer o que você quer fazer.

ACTIONS = {
  1 : "Created",
  2 : "Deleted",
  3 : "Updated",
  4 : "Renamed from something",
  5 : "Renamed to something"
}
FILE_LIST_DIRECTORY = 0x0001

class myThread (threading.Thread):
    def __init__(self, threadID, fileName, directory, origin):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.fileName = fileName
        self.daemon = True
        self.dir = directory
        self.originalFile = origin
    def run(self):
        startMonitor(self.fileName, self.dir, self.originalFile)

def startMonitor(fileMonitoring,dirPath,originalFile):
    hDir = win32file.CreateFile (
        dirPath,
        FILE_LIST_DIRECTORY,
        win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
        None,
        win32con.OPEN_EXISTING,
        win32con.FILE_FLAG_BACKUP_SEMANTICS,
        None
    )
    # Wait for new data and call ProcessNewData for each new chunk that's
    # written
    while 1:
        # Wait for a change to occur
        results = win32file.ReadDirectoryChangesW (
            hDir,
            1024,
            False,
            win32con.FILE_NOTIFY_CHANGE_LAST_WRITE,
            None,
            None
        )
        # For each change, check to see if it's updating the file we're
        # interested in
        for action, file_M in results:
            full_filename = os.path.join (dirPath, file_M)
            #print file, ACTIONS.get (action, "Unknown")
            if len(full_filename) == len(fileMonitoring) and action == 3:
                #copy to main file
                ...

Aqui está um exemplo voltado para assistir arquivos de entrada que não escrever mais de uma linha por segundo, mas geralmente muito menos. O objetivo é acrescentar a última linha (a maioria escrita recente) para o arquivo de saída especificado. Copiei isso a partir de um dos meus projetos e apenas apagado todas as linhas irrelevantes. Você terá que preencher ou alterar os símbolos que faltam.

from PyQt5.QtCore import QFileSystemWatcher, QSettings, QThread
from ui_main_window import Ui_MainWindow   # Qt Creator gen'd 

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        Ui_MainWindow.__init__(self)
        self._fileWatcher = QFileSystemWatcher()
        self._fileWatcher.fileChanged.connect(self.fileChanged)

    def fileChanged(self, filepath):
        QThread.msleep(300)    # Reqd on some machines, give chance for write to complete
        # ^^ About to test this, may need more sophisticated solution
        with open(filepath) as file:
            lastLine = list(file)[-1]
        destPath = self._filemap[filepath]['dest file']
        with open(destPath, 'a') as out_file:               # a= append
            out_file.writelines([lastLine])

É claro, a classe QMainWindow englobando não é estritamente necessário, ou seja. você pode usar QFileSystemWatcher sozinho.

A melhor solução ea mais simples é usar pygtail: https://pypi.python.org/pypi/pygtail

from pygtail import Pygtail

while True:
    for line in Pygtail("some.log"):
        sys.stdout.write(line)

Você também pode usar uma biblioteca simples chamado repyt , aqui está um exemplo:

repyt ./app.py

Parece que ninguém postou fswatch . É um multi-plataforma observador de sistema de arquivos. Basta instalá-lo, execute-o e siga as instruções.

Eu usei-o com programas em Python e golang e ele simplesmente funciona.

Relacionado com o @ solução 4Oh4 uma mudança suave para uma lista de arquivos para assistir;

import os
import sys
import time

class Watcher(object):
    running = True
    refresh_delay_secs = 1

    # Constructor
    def __init__(self, watch_files, call_func_on_change=None, *args, **kwargs):
        self._cached_stamp = 0
        self._cached_stamp_files = {}
        self.filenames = watch_files
        self.call_func_on_change = call_func_on_change
        self.args = args
        self.kwargs = kwargs

    # Look for changes
    def look(self):
        for file in self.filenames:
            stamp = os.stat(file).st_mtime
            if not file in self._cached_stamp_files:
                self._cached_stamp_files[file] = 0
            if stamp != self._cached_stamp_files[file]:
                self._cached_stamp_files[file] = stamp
                # File has changed, so do something...
                file_to_read = open(file, 'r')
                value = file_to_read.read()
                print("value from file", value)
                file_to_read.seek(0)
                if self.call_func_on_change is not None:
                    self.call_func_on_change(*self.args, **self.kwargs)

    # Keep watching in a loop
    def watch(self):
        while self.running:
            try:
                # Look for changes
                time.sleep(self.refresh_delay_secs)
                self.look()
            except KeyboardInterrupt:
                print('\nDone')
                break
            except FileNotFoundError:
                # Action on file not found
                pass
            except Exception as e:
                print(e)
                print('Unhandled error: %s' % sys.exc_info()[0])

# Call this function each time a change happens
def custom_action(text):
    print(text)
    # pass

watch_files = ['/Users/mexekanez/my_file.txt', '/Users/mexekanez/my_file1.txt']

# watcher = Watcher(watch_file)  # simple



if __name__ == "__main__":
    watcher = Watcher(watch_files, custom_action, text='yes, changed')  # also call custom action function
    watcher.watch()  # start the watch going

Eu não conheço nenhuma função específica do Windows. Você poderia tentar obter o hash MD5 do arquivo a cada segundo / minuto / hora (depende de quão rápido você precisar dele) e compará-lo ao último hash. Quando ele difere você sabe que o arquivo foi alterado e você ler as mais recentes linhas.

Eu tentaria algo como isto.

    try:
            f = open(filePath)
    except IOError:
            print "No such file: %s" % filePath
            raw_input("Press Enter to close window")
    try:
            lines = f.readlines()
            while True:
                    line = f.readline()
                    try:
                            if not line:
                                    time.sleep(1)
                            else:
                                    functionThatAnalisesTheLine(line)
                    except Exception, e:
                            # handle the exception somehow (for example, log the trace) and raise the same exception again
                            raw_input("Press Enter to close window")
                            raise e
    finally:
            f.close()

Os laço verifica se há uma nova linha (s) desde arquivo última vez foi lido - se houver, ele é lido e passado para a função functionThatAnalisesTheLine. Se não, roteiro espera 1 segundo e repete o processo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top