一架梯子,一头程序猿,仰望星空!

fsnotify Go实现文件和目录监控

fsnotify是一个用Go编写的文件系统通知库,可以监控文件系统中的文件和目录的变化,并在变化发生时通知应用程序。

fsnotify是一个用Go编写的文件系统通知库,可以监控文件系统中的文件和目录的变化,并在变化发生时通知应用程序。它支持跨平台,可以在Linux、macOS和Windows等不同的操作系统上运行。fsnotify利用了不同操作系统的文件系统通知机制,例如Linux上的inotify、macOS上的FSEvents以及Windows上的ReadDirectoryChangesW。

要求使用Go 1.17或更新版本;完整的文档请参见https://pkg.go.dev/github.com/fsnotify/fsnotify


不同操作系统支持情况:

后端 操作系统 状态
inotify Linux 受支持
kqueue BSD、macOS 受支持
ReadDirectoryChangesW Windows 受支持
FEN illumos 受支持
fanotify Linux 5.9+ 暂未支持
AHAFS AIX aix分支;由于缺乏维护者和测试环境,属于实验性功能
FSEvents macOS 需要x/sys/unix支持
USN Journals Windows 需要x/sys/windows支持
轮询 所有 暂未支持

Linux和illumos应该包括Android和Solaris,但目前尚未进行测试。

应用场景

fsnotify的应用场景包括但不限于以下几种情况:

  1. 实时文件同步:fsnotify可以实时监控文件系统的变化,因此可以用于实现实时文件同步。当源文件发生变化时,可以立即将变化同步到目标文件,保证文件的一致性。
  2. 自动化构建:fsnotify可以监控项目的源代码和依赖文件的变化,从而在变化发生时触发构建命令,实现自动化构建。这样可以节省手动构建的时间和精力,提高开发效率。
  3. 文件备份:fsnotify可以监控需要备份的文件或目录的变化,在文件发生变化时立即备份。这样可以保证数据的安全性,避免因文件丢失或损坏而导致的数据损失。
  4. 实时日志监控:fsnotify可以监控日志文件的创建、修改和删除等操作,从而在日志文件发生变化时触发日志监控程序,实时监控日志内容的变化。
  5. 文件系统安全性监控:fsnotify可以监控文件系统的安全性事件,例如文件的访问、修改和删除等操作。这样可以实现对文件系统的安全性监控,及时发现并记录不安全的事件。

使用示例

一个基本示例:

package main

import (
    "log"

    "github.com/fsnotify/fsnotify"
)

func main() {
    // 创建新的Watcher。
    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    defer watcher.Close()

    // 开始监听事件。
    go func() {
        for {
            select {
            case event, ok := <-watcher.Events:
                if !ok {
                    return
                }
                log.Println("事件:", event)
                if event.Has(fsnotify.Write) {
                    log.Println("修改的文件:", event.Name)
                }
            case err, ok := <-watcher.Errors:
                if !ok {
                    return
                }
                log.Println("错误:", err)
            }
        }
    }()

    // 添加需要监控的路径。
    err = watcher.Add("/tmp")
    if err != nil {
        log.Fatal(err)
    }

    // 阻塞主goroutine。
    <-make(chan struct{})
}

更多示例可以在cmd/fsnotify中找到,并可以使用以下命令运行:

% go run ./cmd/fsnotify

更详细的文档可以在godoc中找到:https://pkg.go.dev/github.com/fsnotify/fsnotify

常见问题解答

当文件移动到另一个目录时,它还会被监视吗?

不会,除非您正在监视它被移动到的位置。

是否监视子目录?

不会,您必须添加对要监视的每个目录的监视(递归监视器在计划中:#18)。

是否需要在goroutine中同时监视错误和事件通道?

是的。您可以使用select在同一个goroutine中读取两个通道(您不需要为两个通道分别启动一个goroutine;请参见示例)。

为什么NFS、SMB、FUSE、/proc或/sys上的通知不起作用?

fsnotify需要底层操作系统的支持才能工作。当前的NFS和SMB协议不提供文件通知的网络级支持,/proc和/sys虚拟文件系统也不提供支持。

这可以通过使用轮询监视器来修复(#9),但尚未实现。

为什么我会收到很多Chmod事件?

一些程序可能会生成大量属性更改,例如macOS上的Spotlight、防病毒程序、备份应用程序和其他一些应用已知会出现此问题。一般来说,忽略Chmod事件通常是最好的做法,因为它们通常没有用处,并且可能引发问题。

在macOS上的Spotlight索引可能会导致多个事件(参见#15)。暂时的解决方法是将您的文件夹添加到Spotlight隐私设置,直到我们有一个原生的FSEvents实现(参见#11)。

观察文件不起作用的好

通常不建议观察单个文件(而不是目录),因为许多程序(尤其是编辑器)会以原子方式更新文件:它会写入一个临时文件,然后将其移动到目标位置,覆盖原文件(或其某个变体)。原文件上的监视器现在已丢失,因为该文件已不存在。

其结果是,断电或崩溃不会导致一个半写入的文件。

观察父目录,并使用Event.Name来过滤掉您不感兴趣的文件。在cmd/fsnotify/file.go中有一个示例。

特定平台的注意事项

Linux

当删除文件时,直到所有文件描述符关闭,REMOVE事件才会被发出;此时会发出CHMOD事件:

fp := os.Open("file")
os.Remove("file")        // CHMOD
fp.Close()               // REMOVE

这是inotify发送的事件,所以对此不能做太多更改。

fs.inotify.max_user_watchessysctl变量指定每个用户的监视数上限,fs.inotify.max_user_instances指定每个用户的inotify实例的最大数目。您创建的每个Watcher都是一个“实例”,您添加的每个路径都是一个“watch”。

这些在/proc中也可以找到,路径为/proc/sys/fs/inotify/max_user_watches/proc/sys/fs/inotify/max_user_instances

要增加它们,可以使用sysctl或将值写入proc文件:

sysctl fs.inotify.max_user_watches=124983
sysctl fs.inotify.max_user_instances=128

要使更改在重启后生效,请编辑/etc/sysctl.conf/usr/lib/sysctl.d/50-default.conf(每个Linux发行版的详细信息有所不同,请查阅您的发行版文档):

fs.inotify.max_user_watches=124983
fs.inotify.max_user_instances=128

达到限制将导致“设备上没有剩余空间”或“打开的文件太多”错误。

kqueue(macOS,所有BSD系统)

kqueue需要为每个被观察的文件打开一个文件描述符;因此,如果您正在观察一个包含五个文件的目录,那就是六个文件描述符。在这些平台上,您将更快地达到系统的“最大打开文件”限制。

可以使用sysctl变量kern.maxfileskern.maxfilesperproc来控制最大打开文件数。


章节目录