关于python:如何查看文件以进行更改?

How do I watch a file for changes?

我有一个日志文件由另一个进程编写,我想要观察更改。 每次发生变化时,我都想读取新数据,对其进行一些处理。

最好的方法是什么? 我希望PyWin32库中有一些钩子。 我找到了win32file.FindNextChangeNotification函数,但不知道如何让它观看特定文件。

如果有人做过这样的事情,我会非常感激听到......

[编辑]我应该提到我是在一个不需要轮询的解决方案之后。

[编辑]诅咒! 这似乎不适用于映射的网络驱动器。 我猜Windows不会像在本地磁盘上那样"听到"文件的任何更新。


你尝试过使用Watchdog吗?

Python API library and shell utilities to monitor file system events.

Directory monitoring made easy with

  • A cross-platform API.
  • A shell tool to run commands in response to directory changes.

Get started quickly with a simple example in Quickstart...


如果轮询对您来说足够好,我只会观察"修改时间"文件统计信息是否发生变化。阅读它:

1
os.stat(filename).st_mtime

(另请注意,Windows本机更改事件解决方案在所有情况下都不起作用,例如在网络驱动器上。)

1
2
3
4
5
6
7
8
9
10
11
12
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...


您是否已查看http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.html上提供的文档?如果您只需要它在Windows下工作,第二个示例似乎正是您想要的(如果您将目录的路径与您要观看的文件之一进行交换)。

否则,轮询可能是唯一真正独立于平台的选项。

注意:我没有尝试过任何这些解决方案。


如果您需要多平台解决方案,请检查QFileSystemWatcher。
这是一个示例代码(未经过消毒):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)


它应该不适用于Windows(可能使用cygwin?),但对于unix用户,您应该使用"fcntl"系统调用。这是Python中的一个例子。如果你需要用C语言写它(相同的函数名),它的代码大致相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)


查看pyinotify。

inotify在较新的linux中替换了dnotify(来自早期的答案),并允许文件级而不是目录级监视。


在对Tim Golden的剧本进行一些黑客攻击之后,我有以下内容似乎很有效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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 )

这可能与负载更多的错误检查有关,但是为了简单地观察日志文件并在将其吐出到屏幕之前对其进行一些处理,这很有效。

感谢大家的意见 - 很棒的东西!


对我来说最简单的解决方案是使用看门狗的工具watchmedo

从https://pypi.python.org/pypi/watchdog我现在有一个进程查找目录中的sql文件并在必要时执行它们。

1
2
3
4
5
watchmedo shell-command \
--patterns="*.sql" \
--recursive \
--command='~/Desktop/load_files_into_mysql_database.sh' \
.

检查我对类似问题的回答。你可以在Python中尝试相同的循环。本页建议:

1
2
3
4
5
6
7
8
9
10
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

另请参阅问题tail()使用Python的文件。


好吧,因为您使用的是Python,所以您只需打开一个文件并继续从中读取行。

1
f = open('file.log')

如果读取的行不为空,则处理它。

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

你可能会错过在EOF上继续调用readline是可以的。在这种情况下,它将继续返回一个空字符串。当某些内容附加到日志文件后,读取将根据需要从停止的位置继续。

如果您正在寻找使用事件或特定库的解决方案,请在您的问题中指明。否则,我认为这个解决方案很好。


以下是Kender代码的简化版本,似乎可以执行相同的操作并且不会导入整个文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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

为了观看具有轮询和最小依赖性的单个文件,这里是一个完全充实的示例,基于Deestan的回答(上图):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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('
Done'
)
                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


这是Tim Goldan脚本的另一个修改,它在linux上运行,并通过使用dict(file => time)添加了一个简单的文件修改器。

用法:whateverName.py path_to_dir_to_watch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/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

正如您在Horst Gutmann指出的Tim Golden的文章中所看到的,WIN32相对复杂并且监视目录,而不是单个文件。

我想建议您研究IronPython,这是一个.NET python实现。
使用IronPython,您可以使用所有.NET功能 - 包括

1
System.IO.FileSystemWatcher

它使用简单的Event接口处理单个文件。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
                ...

这是检查文件是否有变化的示例。一个可能不是最好的方法,但肯定是一个简短的方法。

方便的工具,用于在对源进行更改时重新启动应用程序。我在玩pygame时做了这个,所以我可以看到文件保存后立即发生效果。

在pygame中使用时,请确保'while'循环中的内容放在游戏循环中,即更新或其他任何内容。否则,您的应用程序将陷入无限循环,您将看不到您的游戏更新。

1
2
3
4
5
6
7
8
9
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

如果你想要我在网上找到的重启代码。这里是。 (与问题无关,虽然它可以派上用场)

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

玩得开心让电子做你想做的事。


这是一个示例,用于观察每秒写入不超过一行但通常少得多的输入文件。目标是将最后一行(最近的写入)附加到指定的输出文件。我从我的一个项目中复制了这个,并删除了所有不相关的行。您必须填写或更改缺失的符号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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])

当然,包含QMainWindow类并不是严格要求的,即。你可以单独使用QFileSystemWatcher。


最好最简单的解决方案是使用pygtail:
https://pypi.python.org/pypi/pygtail

1
2
3
4
5
from pygtail import Pygtail

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

相关的@ 4Oh4解决方案可以顺利更改要查看的文件列表;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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('
Done'
)
                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


您还可以使用名为repyt的简单库,这是一个示例:

1
repyt ./app.py

似乎没有人发布过fswatch。它是一个跨平台的文件系统观察者。只需安装它,运行它并按照提示操作即可。

我已经将它与python和golang程序一起使用,它只是起作用。


我不知道任何Windows特定的功能。您可以尝试每秒/分钟/小时获取文件的MD5哈希值(取决于您需要多快)并将其与上一个哈希值进行比较。如果它不同,你知道文件已被更改,你读出最新的行。


我会尝试这样的事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    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()

循环检查自上次读取文件后是否有新行 - 如果有,则读取并传递给functionThatAnalisesTheLine函数。如果没有,脚本等待1秒钟并重试该过程。