暗色模式

inotify 文件监控:从内核事件监听到自动化备份

技术教程
2026-08-24
12
0
本文要点
  • 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_initinotify_add_watchread 等),机制上可以概括为三步:

  1. 创建实例inotify_init() 返回一个文件描述符,相当于一个「事件管道」;
  2. 注册 watchinotify_add_watch() 把某个文件或目录挂到实例上,并声明关心哪些事件类型;
  3. 读取事件:用户态用 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.0

CentOS/Rocky 上包名一样,dnf install -y inotify-tools 即可。

用 inotifywait 监听文件事件

inotifywait 最简单的用法是把要监控的目录作为参数,默认只等一次事件就退出。加 -m(monitor)参数则进入持续监听模式,一直挂着等事件。

我在演示机上实测了这样一组操作:后台启动 inotifywait -m 监听 /tmp/inotify-demo,然后依次对这个目录做「创建 app.log → 写入第一行 → 追加第二行 → 新建子目录 sub → 删除 app.log」,最后查看监听输出:

inotifywait 基础事件监听

对应输出如下:

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 做了创建和追加写入:

inotifywait 递归监听与格式化输出

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:   14080

max_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 拼全路径,脚本缩进后逻辑清晰。后台启动后往监控目录丢两个文件实测:

inotifywait 实时文件自动备份

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) 手册

发表评论

暂无评论,快来抢沙发吧!