如何在Python中获取文件关闭事件

11 投票
4 回答
9417 浏览
提问于 2025-04-17 22:18

在一台运行Windows 7 64位的机器上使用Python 2.7。

如何获取文件关闭事件:

  1. 当文件在一个新的程序进程中打开时(比如记事本、写字板,每次打开文件时都会在新的写字板进程中打开)
  2. 当文件在文件打开程序的一个标签页中打开时(比如Notepad++,它会在新的标签页中打开所有文件,但只会运行一个Notepad++进程)

那么,如何在上述情况下获取文件关闭事件呢?有没有可能通过一段通用的代码来实现这两种情况?我正在处理不同类型的文件。

4 个回答

0

我没有找到一个可以在Windows上捕捉到openclose事件的包。正如其他人提到的,pyinotify是一个非常好的选择,适用于基于Linux的操作系统。

因为我无法监控文件关闭的事件,所以我选择了监控修改事件。这种方法有点像事后诸葛亮(也就是说,我不能在看到文件关闭之前暂停)。不过,这个方法效果出乎意料的好。

我使用了watchdog这个包。下面的代码来自他们的示例实现,如果你在命令行中不传路径,它会监控当前目录,否则会监控你传入的路径。

示例调用:python test.pypython test.py C:\Users\Administrator\Desktop

import sys
import time
import logging
from watchdog.observers import Observer
from watchdog.events import LoggingEventHandler
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s - %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S')
    path = sys.argv[1] if len(sys.argv) > 1 else '.'
    event_handler = LoggingEventHandler()
    observer = Observer()
    observer.schedule(event_handler, path, recursive=True)
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join() 

这段代码会告诉你文件何时被创建、修改、删除或重命名/移动。你可以通过监控on_modified 事件来过滤,只关注修改的情况。

0

你可以使用 Pyfanotify 或者 butter

我觉得这个链接对你很有帮助: 使用C、Python和Ruby处理Linux文件系统事件

在那里你会找到一个示例,正好可以实现你想要的功能(使用 pyinotify),这就是代码:

import pyinotify

DIR_TO_WATCH="/tmp/notify-dir"
FILE_TO_WATCH="/tmp/notify-dir/notify-file.txt"

wm = pyinotify.WatchManager()

dir_events = pyinotify.IN_DELETE | pyinotify.IN_CREATE
file_events = pyinotify.IN_OPEN | pyinotify.IN_CLOSE_WRITE | pyinotify.IN_CLOSE_NOWRITE

class EventHandler(pyinotify.ProcessEvent):
    def process_IN_DELETE(self, event):
        print("File %s was deleted" % event.pathname) #python 3 style print function
    def process_IN_CREATE(self, event):
        print("File %s was created" % event.pathname)
    def process_IN_OPEN(self, event):
        print("File %s was opened" % event.pathname)
    def process_IN_CLOSE_WRITE(self, event):
        print("File %s was closed after writing" % event.pathname)
    def process_IN_CLOSE_NOWRITE(self, event):
        print("File %s was closed after reading" % event.pathname)

event_handler = EventHandler()
notifier = pyinotify.Notifier(wm, event_handler)

wm.add_watch(DIR_TO_WATCH, dir_events)
wm.add_watch(FILE_TO_WATCH, file_events)

notifier.loop()
3

你遇到的问题其实不是出在Python上,而是Windows系统上。虽然可以实现你想要的功能,但你需要写一些不简单的C/C++代码。

在Windows的用户模式下,没有文件打开或关闭的通知。这就是为什么其他人推荐的库没有文件关闭通知的原因。在Windows中,检测用户模式变化的API是ReadDirectoryChangesW。它会提醒你以下几种情况:

  • FILE_ACTION_ADDED:如果有文件被添加到目录中。
  • FILE_ACTION_REMOVED:如果有文件从目录中移除。
  • FILE_ACTION_MODIFIED:如果有文件被修改。这可能是时间戳或属性的变化。
  • FILE_ACTION_RENAMED_OLD_NAME:如果文件被重命名,这里是旧的名字。
  • FILE_ACTION_RENAMED_NEW_NAME:如果文件被重命名,这里是新的名字。

无论你用多少Python,都无法改变Windows提供的功能。

要获取文件关闭的通知,像Process Monitor这样的工具会安装一个Minifilter,它在内核中运行,位于其他过滤器(如EFS)之上。

要实现你想要的功能,你需要:

  1. 安装一个Minifilter,里面有发送事件回用户模式的代码。可以使用微软的Minispy示例,它稳定且快速。
  2. 将用户程序的代码转换成Python扩展(minispy.pyd),这个扩展需要提供一个生成器来产生事件。这是比较难的部分,我会再详细说。
  3. 你需要过滤事件,你可能不敢相信在一个空闲的Windows机器上有多少输入输出操作!
  4. 然后你的Python程序可以导入这个扩展并执行相关操作。

整个过程大致是这样的:

A Python wrapper over a Windows Minifilter for filesystem events

当然,你可以在NTFS上使用EFS,这只是为了说明你的minifilter会在所有这些之上。

难点在于:

  • 你的minifilter必须由微软信任的机构进行数字签名。Verising是一个选择,但还有其他的。
  • 调试需要一个单独的(虚拟)机器,但你可以让你的接口易于模拟。
  • 你需要用具有管理员权限的账户来安装minifilter。任何用户都可以读取事件。
  • 你需要自己处理多用户的问题。对于多个用户来说,只有一个minifilter。
  • 你需要将MiniSpy示例中的用户程序转换成DLL,然后用Python扩展将其包装。

最后两点是最难的。

19

在*nix系统上,这个任务相对简单,但在Windows上,获取文件关闭事件就没那么容易了。下面是按操作系统分类的常用方法总结。

对于Linux

在Linux上,文件系统的变化可以很容易地被监控,并且监控的细节也很丰富。最好的工具是一个叫做inotify的内核功能,还有一个使用它的Python实现,叫做Pynotify。

  • Pyinotify

    Pyinotify是一个用于监控文件系统变化的Python模块。它依赖于Linux内核中的一个功能(在内核2.6.13中合并)叫做inotify,这是一个基于事件的通知器。它的通知通过三次系统调用从内核空间传递到用户空间。Pyinotify将这些系统调用绑定在一起,并在其上提供一个通用和抽象的方式来操作这些功能。

    在这里你可以找到可以用Pynotify监控的事件列表。

    示例用法:

    import pyinotify

    class EventHandler(pyinotify.ProcessEvent):
        def process_IN_CLOSE_NOWRITE(self, event):
            print "File was closed without writing: " + event.pathname
        def process_IN_CLOSE_WRITE(self, event):
            print "File was closed with writing: " + event.pathname
    
    def watch(filename):
        wm = pyinotify.WatchManager()
        mask = pyinotify.IN_CLOSE_NOWRITE | pyinotify.IN_CLOSE_WRITE
        wm.add_watch(filename, mask)
    
        eh = EventHandler()
        notifier = pyinotify.Notifier(wm, eh)
        notifier.loop()
    
    if __name__ == '__main__':
        watch('/path/to/file')
    

对于Windows

Windows的情况比Linux复杂得多。大多数库依赖于ReadDirectoryChanges API,但这个API有一些限制,无法检测到像文件关闭事件这样的细节。不过,还有其他方法可以检测这些事件,继续往下看吧。

  • Watcher

    注意:Watcher最后一次更新是在2011年2月,所以可以考虑跳过这个。

    Watcher是一个低级的C扩展,用于在Windows系统上接收文件系统更新,使用ReadDirectoryChangesW API。这个包还包括一个高级接口,可以模拟大部分.NET的FileSystemWatcher API。
    使用Watcher检测文件关闭事件的最接近的方法是监控FILE_NOTIFY_CHANGE_LAST_WRITE和/或FILE_NOTIFY_CHANGE_LAST_ACCESS事件。

    示例用法:

    import watcher
    w = watcher.Watcher(dir, callback)
    w.flags = watcher.FILE_NOTIFY_CHANGE_LAST_WRITE
    w.start()
    
  • Watchdog

    这是一个用于监控文件系统事件的Python API和命令行工具。安装很简单:$ pip install watchdog。更多信息请访问文档
    Watchdog在Windows上依赖于ReadDirectoryChangesW API,这和Watcher以及其他依赖同一API的库一样,有一些限制。

  • Pywatch

    这是一个几乎是Linux watch命令的Python克隆。pywatch.watcher.Watcher类可以被设置为监控一组文件,并在这些文件发生变化时执行一组命令。它只能监控文件变化事件,因为它依赖于轮询stat的st_mtime

Windows NTFS的额外功能:

  • NTFS USN Journal

    NTFS USN(更新序列号)日志是NTFS的一个功能,它记录了对卷所做的更改。之所以把它列为额外功能,是因为它不是一个特定的库,而是存在于NTFS系统上的一个功能。所以如果你使用的是其他Windows文件系统(如FAT、ReFS等),这个就不适用了。
    它的工作原理是,系统在USN日志文件中记录对卷所做的所有更改,每个卷都有自己的实例。更改日志中的每条记录包含USN、文件名和更改的信息。

    这个方法之所以对这个问题有趣,是因为它提供了一种检测文件关闭事件的方法,这个事件被定义为USN_REASON_CLOSE。更多信息和完整的事件列表可以在这个MSDN文章中找到。关于USN日志的完整文档,请访问这个MSDN页面

    从Python访问USN日志的方法有很多,但唯一成熟的选项似乎是ntfsjournal模块。

Windows的“正确”方法:

  • 文件系统过滤驱动程序

    正如MSDN页面上所描述的:

    文件系统过滤驱动程序是一个可选的驱动程序,它为文件系统增加了价值或修改了其行为。文件系统过滤驱动程序是一个内核模式组件,作为Windows执行的一部分运行。文件系统过滤驱动程序可以过滤一个或多个文件系统或文件系统卷的I/O操作。根据驱动程序的性质,过滤可以意味着记录、观察、修改,甚至阻止。文件系统过滤驱动程序的典型应用包括杀毒软件、加密程序和分层存储管理系统。

    实现一个文件系统过滤驱动程序并不简单,但如果有人想尝试,可以在CodeProject上找到一个很好的入门教程。

    附注:查看@ixe013的回答,获取关于这种方法的更多信息。

跨平台

  • Qt的QFileSystemWatcher

    QFileSystemWatcher类提供了一个接口,用于监控文件和目录的修改。这个类是在Qt 4.2中引入的。
    不幸的是,它的功能相对有限,因为它只能检测文件是否被修改、重命名或删除,以及当新文件被添加到目录时。

    示例用法:

    import sys
    from PyQt4 import QtCore
    
    def directory_changed(path):
        print('Directory Changed: %s' % path)
    
    def file_changed(path):
        print('File Changed: %s' % path)
    
    app = QtCore.QCoreApplication(sys.argv)
    
    paths = ['/path/to/file']
    fs_watcher = QtCore.QFileSystemWatcher(paths)
    fs_watcher.directoryChanged.connect(directory_changed)
    fs_watcher.fileChanged.connect(file_changed)
    
    app.exec_()
    

撰写回答