本文要点
- Linux 的
inotify是内核级文件系统事件通知机制,文件被创建、修改、删除、移动时能立即收到事件,无需轮询扫描。 - Ubuntu/Debian 用一条
apt install inotify-tools装好命令行工具,核心是常驻监听的inotifywait。 inotifywait -m -e create,modify,delete持续监听指定目录,配合-r可递归监控整个目录树,生成的事件是「路径 + 事件类型 + 文件名」的真实输出。- 用
--format '%T %e %w%f'+--timefmt可以把输出改成带时间戳的自定义格式,方便直接进日志系统。 - 内核有三项监控上限(实例数、事件队列、watch 数量),监控海量文件读满报错时可通过
/proc/sys/fs/inotify/*调大。 - 实测演示:一个几行脚本用
inotifywait管道监听上传目录,新文件一落地就自动复制到备份目录并记录日志。
为什么需要文件监控
运维里常常要回答「某个文件什么时候变了」:配置文件被谁改过、上传目录里多了什么、日志文件什么时候被轮转、证书是否即将过期。最朴素的办法是定时任务每几分钟扫描一遍目标目录,比对文件列表和时间戳——这就是「轮询」。它能用,但有两个硬伤:一是延迟,改动发生的时刻和被发现的时间差最多一个轮询周期;二是浪费,目录一大会上千个文件时,每次扫描都是实打实的磁盘读和 CPU 消耗。
Linux 内核从 2.6.13 开始提供了一个更好的方案:inotify。它让内核在文件系统事件发生时主动通知你,而不是让你反复去问。文件被创建、写入、删除、移动、属性变化,都会产生对应的内核事件,应用程序收到通知后按需处理。这样既没有轮询的延迟,也没有扫描的开销,是日志系统、文件同步工具、编辑器、杀毒软件等都在用的基础机制。
inotify 是什么
inotify 是内核提供的一组系统调用(inotify_init、inotify_add_watch、read 等),机制上可以概括为三步:
- 创建实例:
inotify_init()返回一个文件描述符,相当于一个「事件管道」; - 注册 watch:
inotify_add_watch()把某个文件或目录挂到实例上,并声明关心哪些事件类型; - 读取事件:用户态用
read()从这个描述符上取事件,事件按类型结构体返回,包含 watch 描述符、事件掩码和文件名。
由此衍生出的问题就很直观:给谁监控?监控什么?事件来了怎么办? 命令行工具 inotifywait 把这三步封装成了容易上手的「监听 + 打印」:指定路径和事件类型,它就一直等着,事件一到就按格式打印出来。
虽然可以直接写 C 代码调 inotify 系统调用,但对绝大多数运维和自动化脚本来说,用 inotify-tools 软件包提供的两个命令就够了:
| 命令 | 作用 |
|---|---|
inotifywait | 等待/持续监听文件事件并输出,核心工具 |
inotifywatch | 统计一段时间内各类事件的数量,适合做触发次数统计 |
本文以 inotifywait 为主线,在 Ubuntu 24.04 上实测演示从监听、递归、格式化到自动备份脚本的完整链路。
安装 inotify-tools
Ubuntu 24.04 / Debian 的软件源里直接有包:
apt install -y inotify-tools装完确认版本:
inotifywait --version
# 输出示例: inotifywait 3.22.6.0CentOS/Rocky 上包名一样,dnf install -y inotify-tools 即可。
用 inotifywait 监听文件事件
inotifywait 最简单的用法是把要监控的目录作为参数,默认只等一次事件就退出。加 -m(monitor)参数则进入持续监听模式,一直挂着等事件。
我在演示机上实测了这样一组操作:后台启动 inotifywait -m 监听 /tmp/inotify-demo,然后依次对这个目录做「创建 app.log → 写入第一行 → 追加第二行 → 新建子目录 sub → 删除 app.log」,最后查看监听输出:

对应输出如下:
Setting up watches.
Watches established.
/tmp/inotify-demo/ CREATE app.log
/tmp/inotify-demo/ MODIFY app.log
/tmp/inotify-demo/ MODIFY app.log
/tmp/inotify-demo/ CREATE,ISDIR sub
/tmp/inotify-demo/ DELETE app.log每一行就是一个事件:路径 + 事件类型 + 文件名。可以看到几个细节:
CREATE只出现一次,但MODIFY出现了两次——因为echo hello >和echo more >>各触发了一次写入,这正好说明一次文件写入可能产生多个MODIFY;mkdir sub产生的是带ISDIR标记的CREATE事件,表示变化对象是目录;- 文件被删除时触发
DELETE,事件是按顺序到达的,和实际操作一一对应。
常用事件类型
-e 参数用来限定关心的类型,不写默认监控所有类型。inotifywait --help 里列出了完整清单,日常最常用的是这几个:
| 事件 | 触发时机 | 常见用途 |
|---|---|---|
create | 在监控目录内新建文件或目录 | 捕获新文件落地 |
modify | 文件内容被写入/追加 | 跟踪文件被编辑 |
close_write | 文件被以可写方式打开后关闭 | 判断「写完收工」,最常用 |
delete | 目录内的文件或目录被删除 | 清理监控 |
moved_from / moved_to | 文件被移出/移入监控目录 | 跟踪重命名、移动 |
access | 文件被读取 | 审计敏感文件被读 |
attrib | 属性变化(权限、时间戳等) | 发现 chmod、touch |
一个小坑:实现「文件同步上传后再处理」这类场景时,别只听modify——编辑器或上传工具往往是分多次写入的,modify会触发一大堆事件。更可靠的信号是close_write,它表示文件已被完整关闭(写完收起),此时再处理最稳妥。这也是下文备份脚本选择create监听后直接复制的原因之一。
递归监听整个目录树
默认 inotifywait 只监控给定的目录本身,子目录里的变化不报。加 -r(recursive)参数后,它会递归为目录树里的每一层都建立 watch,子目录的文件变化同样能看到。我实测监控了带子目录的 /tmp/inotify-rec,对其中 sub/a.conf 做了创建和追加写入:

08:04:13 CREATE /tmp/inotify-rec/sub/a.conf
08:04:13 MODIFY /tmp/inotify-rec/sub/a.conf
08:04:14 MODIFY /tmp/inotify-rec/sub/a.conf注意两点:
- 第一行是子目录
sub里的文件创建,而-r前我并没有显式监听sub——这就是递归的效果; - 时间戳不是自动带的,这里用了自定义格式化输出。
自定义输出:--format 与 --timefmt
默认输出是「路径 + 事件 + 文件名」的固定格式,不方便读也不方便做抓取。inotifywait 提供 --format 自定义输出模板,配合 --timefmt 指定时间格式。上图命令的核心是:
inotifywait -q -m -r -e create,modify \
--format '%T %e %w%f' \
--timefmt '%H:%M:%S' \
/tmp/inotify-rec%T:事件时间(格式由--timefmt决定);%e:事件类型(多个事件用英文逗号分隔);%w:被监控目录的路径(含结尾/);%f:发生变化的文件名;-q:quiet 模式,省略开头的 "Setting up watches." 提示行,输出更干净。
--timefmt 的格式和 date 命令完全一致(%Y-%m-%d、%H:%M:%S 等都能用)。%w%f 直接拼出完整文件路径,这段输出可以原样喂给日志系统或 grep 过滤。
内核监控上限与调优
inotify 是内核资源,总要有所限制,否则任何进程都能无限挂 watch 会拖垮系统。内核通过三个参数控制每用户可用的量:
cat /proc/sys/fs/inotify/max_user_instances # 每个用户最多创建多少个 inotify 实例
cat /proc/sys/fs/inotify/max_queued_events # 事件队列最长(未及时读走的事件数)
cat /proc/sys/fs/inotify/max_user_watches # 每个用户最多注册多少个 watch在 Ubuntu 24.04 演示机上实测默认值:
max_user_instances: 128
max_queued_events: 16384
max_user_watches: 14080max_user_watches 最容易触顶——递归监控一个大型目录树(比如上万个文件的日志目录、用户目录),瞬间就把一万四千个 watch 用光了,inotifywait 会报 No space left on device 或 Starting too many things 之类的错误。调大的方法:
sysctl -w fs.inotify.max_user_watches=1048576
# 永久生效(重启保留)
echo 'fs.inotify.max_user_watches=1048576' >> /etc/sysctl.d/90-inotify.conf
sysctl --system事件积压超过 max_queued_events 时内核会丢弃事件并记录 IN_Q_OVERFLOW,监听方只能知道「中间丢了数据」却不知道丢了哪些,对强一致要求高的同步场景需要留意队列容量。
实际场景:新文件实时自动备份
把上面的能力组合起来,就是运维里很实用的小工具:监控一个传入目录,新文件一出现就自动复制到备份目录并打日志。比如邮件附件落地目录、用户上传目录、FTP 接收目录。脚本如下(存为 /tmp/auto-backup.sh):
#!/bin/bash
# 用 inotifywait 监听新文件并自动备份
WATCH=/tmp/inotify-watch
BACKUP=/tmp/inotify-bak
inotifywait -m -q -e create --format '%f' "$WATCH" | while read f; do
cp "$WATCH/$f" "$BACKUP/$f" && echo "$(date +%T) 已备份 $f"
done关键点在于把 inotifywait 的正常输出用管道接给了 while read 循环:它每收到一行(就是一个文件名),就执行一次复制并打印结果。因为 --format '%f' 只输出文件名不带路径,循环里复制用 $WATCH/$f、目标用 $BACKUP/$f 拼全路径,脚本缩进后逻辑清晰。后台启动后往监控目录丢两个文件实测:

mkdir -p /tmp/inotify-watch /tmp/inotify-bak
bash /tmp/auto-backup.sh > /tmp/bak.log 2>&1 &
echo '20260824_report.pdf' > /tmp/inotify-watch/report.pdf
echo 'photo.jpg' > /tmp/inotify-watch/photo.jpg
ls -l /tmp/inotify-bak # 查看备份目录
cat /tmp/bak.log # 查看运行日志=== 备份目录 ===
total 8
-rw-r--r-- 1 root root 10 Aug 24 08:05 photo.jpg
-rw-r--r-- 1 root root 20 Aug 24 08:05 report.pdf
=== 运行日志 ===
08:05:31 已备份 report.pdf
08:05:32 已备份 photo.jpg两个文件一落地,备份目录和日志文件几乎同步出现了对应记录——这就是事件驱动相对定时轮询的即时性。把 cp 换成任意处理逻辑(如调用压缩、同步上传对象存储、触发 webhook),就得到一种通用的「文件到站即处理」流水线。需要常驻时,可以把它托管成 systemd 服务单元(编写、启停与日志排查看 Linux systemd 服务管理:从 systemctl 到编写自己的 service 单元)或借助 supervisord 等进程守护工具运行。
常见问题与技巧
- 递归大目录报错:
max_user_watches触顶,按上文用sysctl调大,或缩小监控范围只对需要的子目录建 watch。 - 改了文件却频繁触发:一次编辑往往有多次
modify,业务处理请优先用close_write而非modify。 -m忘了加,监听一次就退出:持续监控必须带-m,否则inotifywait等到第一个事件就打印退出。- 想监控「整个文件系统」:inotify 一般不建议直接挂在
/上,事件量会爆炸;按目录范围-r控制边界。 - 管道处理脚本注意吞错:
while read读不到文件名时循环体不会执行,可加set -o pipefail并在命令行排查inotifywait是否有报错输出(如 watch 上限)。
总结
inotify 把「主动问」变成了「被动等」,是 Linux 文件系统监控的正确姿势。本文的核心链路是:apt install inotify-tools 装好工具 → inotifywait -m -e 事件 目录 常驻监听 → 需要时加 -r 递归、用 --format/--timefmt 自定义输出 → 监控大规模目录树时调大内核 watch 上限 → 把 inotifywait 的输出接进脚本循环,实现文件到站即处理的自动化。
参考:inotify-tools 官方文档、Linux 手册页 inotify(7)、inotifywait(1) 手册。
评论 (0)
暂无评论,快来抢沙发吧!