fsnotify不支持通配符过滤,必须监听目录后手动判断后缀;add("*.yaml")失败因系统接口不支持通配符,仅接受真实路径;安全过滤需用filepath.ext()转小写比较,并排除临时文件;递归监听须手动遍历子目录并add,且create目录事件后需立即add新目录。

直接监听特定后缀的文件,不能靠 fsnotify 的 API 参数过滤——它不提供 glob 或后缀匹配功能,所有过滤必须在事件到达后手动做。
为什么 fsnotify.Add("*.yaml") 会失败
fsnotify 底层调用的是操作系统原生接口(inotify/kqueue/ReadDirectoryChangesW),这些系统机制本身就不支持通配符路径。传入 *.yaml 会被当作字面路径解析,结果是 no such file or directory 或静默忽略。
-
watcher.Add()只接受真实存在的目录路径或(极少数情况下)文件路径,且文件路径在 Linux/macOS 下通常无效 - 想“监听所有 .yaml 文件”,唯一合法做法是监听其所在目录,再在
event.Name上做字符串或后缀判断 - 别尝试用正则预注册路径——
filepath.WalkDir遍历时仍得对每个IsDir()结果调watcher.Add(),而文件本身不能 Add
如何安全过滤 .env、.toml、.json 等目标后缀
收到事件后,用 filepath.Ext() 提取扩展名比字符串 strings.HasSuffix() 更可靠,能正确处理 Windows 路径分隔符和大小写问题(如 .ENV)。
- 优先用
filepath.Base(event.Name)获取文件名,再用filepath.Ext()判断后缀,避免误判路径中带点的目录名(如/config.prod/v1.2.0/app.yaml) - 扩展名比较建议转小写:
strings.ToLower(filepath.Ext(name)) == ".yaml" - 编辑器临时文件(
.swp、~、.tmp)必须排除,否则会触发脏事件;可在同一判断分支里加!strings.HasPrefix(filepath.Base(name), ".") && !strings.HasSuffix(name, "~") - 不要只看
event.Op == fsnotify.Write,要写成event.Op&fsnotify.Write != 0,否则漏掉Write | Chmod这类组合事件
递归监听 + 后缀过滤的典型陷阱
你调 watcher.Add("configs/") 只监听该目录自身变更(比如被重命名),子目录里的 db.yaml 修改根本不会触发事件——这是最常被忽略的设计事实。
- 递归必须手动实现:用
filepath.WalkDir("configs/", ...)遍历,对每个entry.IsDir()为 true 的路径调一次watcher.Add(path) - 遍历时跳过符号链接(除非明确需要),否则 macOS 下可能监听失效,Windows 下可能触发重复事件
- 新增子目录时,仅靠监听
Create事件不够——收到event.Op&fsnotify.Create != 0 && event.IsDir()后,需立刻watcher.Add(event.Name),否则后续该目录下文件变化收不到 - 并发调
watcher.Add()可能 panic,加sync.Mutex保护,或改用单 goroutine 串行注册
真正难的不是写对 filepath.Ext(),而是理解 fsnotify 从不“知道”你要什么文件——它只告诉你“某个目录里发生了某类操作”,剩下的白名单、去重、原子写入兜底、跨平台行为收敛,全得你自己接住。漏掉任意一环,热加载就可能卡在旧配置里不动。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











