每分钟跑一次的脚本,毛病在哪
"看看目录里有没有新文件"这种需求,很多人第一反应是写个 cron 每分钟跑一遍。它确实能用,代价是一堆你迟早会遇到的麻烦:上一轮还没跑完下一轮就起来了,你得自己加锁;文件落到一半就被扫到,处理出来是半个文件;绝大多数分钟里它什么也没干,白占一次调度。
Linux 早就有更合适的东西:inotify。内核 2.6.13 起并入,应用注册好监视点后就阻塞等事件,文件一有动静内核主动通知,不用轮询。
三条路,选哪条
- 直接调 C API,除非你在写系统级程序。
- 命令行工具 inotifywait / inotifywatch(Debian 的 inotify-tools 包),适合临时调试;inotifywait 的 daemon 模式只能把事件记进日志,干不了活。
- Python 库,写常驻脚本最省事。
装什么
Debian 13(trixie)可以直接 apt -y install python3-inotify,仓库里是 0.2.10-5,上游就是这个库;习惯用 pip 的就 pip install inotify,两边模块名都是 inotify。要提醒一句:早年有个同名的 PyInotify 项目已经停维护,跟现在这个库没有关系,装混了 import 会对不上。守护进程化再补一个 apt -y install python3-daemonize。
先跑通一个能看见事件的脚本
#!/usr/bin/python3
import inotify.adapters
i = inotify.adapters.InotifyTree('/tmp/test')
for event in i.event_gen(yield_nones=False):
(_, type_names, path, filename) = event
print("PATH=[{}] FILENAME=[{}] EVENT_TYPES={}".format(path, filename, type_names))
chmod 755 后跑起来,另开一个终端在 /tmp/test 里 touch、写、chmod、删,你会看到一串 IN_CREATE、IN_OPEN、IN_MODIFY、IN_CLOSE_WRITE、IN_DELETE。这一步很重要——先看清一个动作会触发几个事件,再决定你的脚本该认哪个。
变成开机就跑的常驻服务
把要监视的目录、要调用的脚本、要认的事件三行配置抽出来,用 daemonize 包把它做成守护进程,再配一个 systemd unit:ExecStart 指向脚本,systemctl daemon-reload && systemctl enable --now watchfs。触发脚本拿到的参数是文件名,剩下爱干什么干什么——压缩、转码、同步、发通知,都在这一个 shell 脚本里。
认哪个事件,别选错
- 上传或写入场景用 IN_CLOSE_WRITE(可写文件被关闭时触发);IN_CREATE 触发的那一刻文件可能才刚建好、内容还没写完。
- rsync、sftp 这类先写临时文件再改名的,认 IN_MOVED_TO。
- 想一次认多个,就在循环里遍历 type_names,别只判断第一个。
三个一定会踩的坑
- 目录监视不递归。 内核只给你一层,像 InotifyTree 这种递归监视是库在用户态给子目录补 watch,新建目录和目录里立刻出现的文件之间存在竞态,理论上会漏事件;watch 总数还受
/proc/sys/fs/inotify/max_user_watches限制。目录树很大时,不如只盯一两个入口目录。 - 事件队列会溢出。 事件爆发时内核直接丢,只给你一个 IN_Q_OVERFLOW。所以触发脚本要写成幂等的,跑一遍和跑三遍结果一样,重要场景另外加一次定时全量对账,别假设每个文件都会恰好被触发一次。
- 网络文件系统不触发。 NFS 之类的远端改动 inotify 收不到,这种场景只能退回轮询。
用在哪最有手感
- 用户上传目录:图片落地自动压缩转码,再挪进 web 目录。
- 静态站:内容目录一变就自动重建并同步到线上。
- 备份链:备份脚本写完一个 flag 文件,下一步(校验、上传异地、发通知)自动接上。
- 配置热加载:改完配置自动 reload,不用记着敲命令。
这套东西的价值不在省那几次 cron,而在于把"文件到了"变成一个可靠的触发信号。补一句:本站没在自己机器上跑过这套代码,命令与行为依据的是内核手册和上游库的说明。
登录 后参与评论