最近在折腾本地知识库索引方案的时候,遇到了一个很常见的问题:每次新增或修改笔记之后,需要手动触发一次索引重建脚本,时间长了非常容易忘记。轮询方案(每隔几秒扫描一次目录)倒是能解决问题,但代价是 CPU 和 I/O 的持续消耗,在有几万个文件的知识库里显得格外浪费。这时候朋友推荐了 [[fswatch]],用了之后觉得这才是正确的打开方式。

fswatch 是什么
fswatch 是一个开源的跨平台文件系统监控工具,由 Enrico M. Crisostomo 开发并维护,项目托管在 GitHub 上。它的核心思路很简单:订阅操作系统提供的文件系统事件通知接口,当指定目录或文件发生变化时,立即输出变化的路径,或者直接触发用户定义的命令。
和轮询方案最大的区别在于,fswatch 完全依赖内核级别的事件推送,自己不做任何定时扫描。在 macOS 上它使用 [[FSEvents]] API,这是苹果系统内建的机制,[[Spotlight]] 和 [[Time Machine]] 都依赖它工作。在 Linux 上则对应 [[inotify]] 或 [[kqueue]],在 BSD 系统上使用 kqueue。这意味着不管你的目录里有多少文件,监控本身对系统资源的占用几乎可以忽略不计,事件延迟也在毫秒级别。
安装
macOS 用户直接通过 [[Homebrew]] 安装,一行命令搞定:
brew install fswatch
Linux 上可以通过各发行版的包管理器安装,或者从源码编译。Ubuntu/Debian 用户可以:
sudo apt install fswatch
安装完成后运行 fswatch --version 确认版本即可。
基本用法
最基础的用法是直接监控一个目录,有变化时把变化的文件路径打印到标准输出:
fswatch /path/to/dir
每次有文件被创建、修改或删除,对应的路径就会出现在终端里。如果想把这个输出接入自动化脚本,最常见的模式是配合 xargs 使用:
fswatch -o /path/to/dir | xargs -I{} your-command
-o 参数让 fswatch 不输出具体路径,只输出一个数字(触发的事件总数),这样 xargs 每次只会执行一次命令,适合需要批量重建而不是逐文件处理的场景。
常用参数
fswatch 提供了丰富的过滤选项,让你精确控制哪些变化值得关注。
--include 和 --exclude 可以用正则表达式过滤路径,两者可以组合使用:
# 只监控 .md 文件的变化,忽略其他所有文件
fswatch --include='\.md$' --exclude='.*' /path/to/dir
注意这里 --exclude='.*' 先排除所有,再用 --include 把需要的加回来,顺序很重要,fswatch 按规则声明的先后顺序依次匹配。
-r 参数开启递归监控,会深入子目录:
fswatch -r /path/to/dir
--event 可以限定只监听特定类型的事件,比如只关心文件内容修改而不关心属性变化:
fswatch --event Updated --event Created /path/to/dir
实际应用场景
我自己用 fswatch 最多的场景是本地知识库的自动化索引。我的 [[Obsidian]] 笔记库有几万个文件,每次手动重建 SQLite 全文索引很麻烦。加了 fswatch 之后,只要有 .md 文件变动,索引就自动增量更新:
fswatch -o --include='\.md$' --exclude='.*' ~/Sync/wiki \
| xargs -I{} python3 ~/Sync/wiki/scripts/build_index.py
另一个常用场景是前端开发时自动触发构建。虽然现在很多构建工具([[Vite]]、[[webpack]])自带 watch 模式,但遇到需要自己写脚本处理的情况,fswatch 比自己实现一个轮询循环要靠谱得多:
fswatch -o src/ | xargs -I{} npm run build
还有一个不太常见但很实用的场景:监控日志文件,某些关键字出现时发送桌面通知:
fswatch /var/log/app.log | while read path; do
if grep -q "ERROR" "$path"; then
osascript -e 'display notification "检测到错误日志" with title "监控告警"'
fi
done
使用时需要注意的地方
fswatch 默认是异步输出,如果短时间内有大量文件变动(比如 git checkout 切换分支),会产生大量事件并发触发,导致命令被执行很多次。这种情况下可以配合 --batch-marker 或者在脚本里加一个简单的防抖逻辑,避免短时间内重复执行。
另外,在 macOS 上 FSEvents 的事件粒度是目录级别的,也就是说即使只改了一个文件,fswatch 输出的路径也可能是该文件所在的目录,而不是具体的文件名。这个行为在某些需要精确文件路径的场景下需要额外处理。如果需要更精确的粒度,可以尝试 --monitor=poll_monitor 切换到轮询模式,但这样就失去了零开销的优势。
还有一点是 fswatch 进程本身不会自动重启。如果监控进程意外退出,需要有额外的进程守护机制(比如 [[launchd]] 或 [[supervisor]])来保证它一直在运行。
最后
fswatch 让我彻底告别了”改完文件别忘了手动跑脚本”的习惯,把那些本应自动化的步骤真正变成了自动化。它的设计哲学和 Unix 的管道思想高度契合——做好一件事,然后通过标准输出和其他工具组合。对于 macOS 用户来说,基于 FSEvents 的实现让监控本身几乎没有额外开销,不管库里有多少文件都能保持响应灵敏。如果你有任何”文件变化时自动做 X”的需求,fswatch 几乎是最低成本、最直接的解决方案。