fsnotify 只能监听目录而非文件,需用 filepath.Dir() 获取父目录并监听;编辑器原子写入需同时处理 Write 和 Rename 事件,优先响应后者;递归监听须手动遍历注册;Events 通道必须带缓冲且及时消费,否则丢事件。

fsnotify 监听不到文件修改,不是库有问题,而是你监听了错误的路径类型——它只接受目录,不接受纯文件路径。直接调 watcher.Add("config.yaml") 在 Linux/macOS 下静默失败,在 Windows 下可能 panic。
为什么 watcher.Add("file.txt") 总是没反应
因为 fsnotify 底层依赖操作系统事件机制(inotify/kqueue/ReadDirectoryChangesW),这些机制本身就不支持“监听单个文件”。你传入一个文件路径,watcher.Add() 会尝试注册该路径的 inode 或句柄,但文件路径没有可监听的目录项,结果是:no such file or directory 或 invalid argument,且不报错。
- 必须改用
watcher.Add(filepath.Dir("file.txt")),即监听其父目录 -
event.Name是相对路径(如"file.txt"),需用filepath.Base(event.Name) == "file.txt"过滤,别用字符串匹配含路径分隔符的全路径 - 若路径含软链,先调
filepath.EvalSymlinks()解析真实路径再取Dir,否则监听可能注册到符号链接自身而非目标目录
如何正确捕获编辑器保存后的最终内容
Vim、VS Code 等默认启用原子写入:先写临时文件(如 .file.txt.swp 或 file.txt~),再 rename 覆盖原文件。这会导致原 inode 失效,仅监听 Write 会收到脏临时文件事件,或完全收不到。
- 必须同时监听
fsnotify.Write和fsnotify.Rename,且优先响应后者——Rename才代表“保存完成” - 判断逻辑用位运算:
if event.Op&fsnotify.Rename != 0 && filepath.Base(event.Name) == "file.txt" - 某些编辑器(如 macOS TextEdit)还会夹带
Chmod,别用strings.Contains(event.String(), "RENAME"),那不可靠 - 收到事件后,建议立刻
os.Stat(event.Name)确认文件存在、非空、不是临时后缀(如~、.swp、.tmp)
递归监听子目录必须手动实现
watcher.Add("/a") 只监听 /a 目录自身的元数据变更(比如被重命名),对 /a/b/c.txt 的修改完全无感。没有 recursive: true 这种参数。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 启动时全量注册:用
filepath.WalkDir(root, ...)遍历,对每个entry.IsDir()为true的路径调watcher.Add(path) - 运行时动态响应:监听到
fsnotify.Create且event.IsDir()为true时,立即watcher.Add(event.Name) - 加
sync.Mutex保护Add调用,避免多 goroutine 并发注册冲突 - 跳过符号链接:
if entry.Type()&os.ModeSymlink != 0,否则可能触发权限错误或无限递归
Events channel 不消费就会丢事件
watcher.Events 是无缓冲 channel,内部 goroutine 往里发事件时,若无人接收,会立即阻塞。持续阻塞会导致后续事件无法写入,最终 watcher 内部死锁或事件永久丢失。
- 必须用带缓冲的中转 channel,例如
events := make(chan fsnotify.Event, 100) - 另起 goroutine 拉取
watcher.Events并转发到events,不要直接range watcher.Events - 关闭前务必停掉所有读
Events的 goroutine;推荐用context.WithCancel控制生命周期 -
watcher.Close()后,已进入 channel 的事件仍会被读到,但继续往已关闭的Events发送会 panic,所以关闭后应设watcher.Events = nil并在select中做空值防护
真正容易被忽略的是:macOS 上 event.Name 在高频小文件修改时可能为空字符串,Linux 下 /proc/sys/fs/inotify/max_user_watches 太小会导致添加失败却只报 no space left on device,而 Windows 对长路径(>260 字符)支持差——这些都不是代码逻辑问题,是平台约束,得提前查、提前配。

















