inotify监控文件夹
参考方案
https://docs.rockylinux.org/zh/books/learning_rsync/06_rsync_inotify/
引子
由于项目要求,需要使用定时任务采集某个目录下的文件进行解析;
需求很简单,实现起来也不复杂:我们只需要根据文件的创建时间(或者文件的最后修改时间) 和定时任务上一次的执行时间作对比,如果发现文件创建时间(或者文件的最后修改时间)大于上一次执行的时间,说明文件需要解析,否则则忽略;
但是实现的时候发现了一个问题: 由于文件的创建时间或者最后修改时间作为文件的元数据在文件中进行存储,复制或者移动该文件时这个元数据并不会发生变化;
所以就会出现一种情况:文件是2024年7月1号创建的,但是该文件并没有立刻放入到该采集文件中,假设:定时任务在7月12号 18点执行完毕后,用户将这个文件放入到采集文件夹,在用户看来这个文件应该被采集,但实际上这个文件的创建时间却是:7月1日,实际上并不会被采集。
这篇文件就是用来解决这个问题的。
一、实现思路
由于采集文件夹是放在 SMB 共享盘中的,所以归拢了一下解决方案如下:
- SMB服务中添加配置,放入SMB共享盘的文件自动修改最后更新时间为当前时间 (上策)
- 监控采集文件夹,所有针对于创建或者移动的事件监听,然后将该文件的最后修改时间设置为当前时间 (下策)
1.1、方案一
网上搜索了一大圈,发现 SMB 并没有提供对应的操作方式,所以这种最优的方案就被扼杀在摇篮中了;
1.2、方案二
由于服务器是使用的 centos,这种监听文件的方案还是很多的,例如:
auditd
、fswatch
、inotify
还有 Systemd 文件单元
;
经过了解发现:Systemd 文件单元
只能够发现文件被操作了,但是不能够获取到 具体是那个文件,这不是鸡肋嘛,直接pass;
最终选择了方案比较成熟的:inotify
。
二、inotifywait的实现
inotifywait
是一个用于监控文件系统事件的命令行工具,它基于 Linux 内核的 inotify 机制。
inotifywait
命令可以实时监控文件或目录的事件,例如文件的创建、删除、修改等,并在事件发生时执行指定的操作。
2.1、inotify-tools详解
inotify-tools工具有两个命令,分别是:
-
inotifywait:用于持续监控,实时输出结果。 它通常与 rsync 增量备份工具一起使用。 因为它是对文件系统的监控,所以可以搭配脚本一起使用。
-
inotifywatch:用于短期监控,任务完成后输出结果。
inotifywait 主要有以下选项:
- -m 表示持续监控
- -r 递归监控
- -q 简化输出信息
- -e 指定监控数据的事件类型,多个事件类型用英文状态的逗号隔开
事件类型如下:
事件类型 | 说明 |
---|---|
access | 文件或目录的内容进行访问 |
modify | 文件或目录的内容被写入 |
attrib | 文件或目录的属性被修改 |
close_write | 文件或目录在可写模式下打开后关闭 |
close_nowrite | 文件或目录在以只读模式打开后关闭 |
close | 无论读/写模式,文件或目录关闭 |
open | 文件或者目录被打开 |
moved_to | 有文件或目录移动到监控的目录中 |
moved_from | 有文件或目录从监控的目录中移出 |
move | 有文件或者目录,被移动到监控目录或者从监控目录中移出 |
move_self | 已移动受监视的文件或目录 |
create | 被监控的目录内有创建的文件或目录 |
delete | 被监控的目录内有文件或目录被删除 |
delete_self | 文件或目录已经被删除 |
unmount | 包含已卸载文件或目录的文件系统 |
inotifywait
的 --format
参数允许你指定输出的格式,以便更灵活地处理监控事件。
下面是一些常见的格式参数及其含义:
- %w:文件或目录的路径名。
- %f:事件发生的文件名(不含路径)。
- %e:事件的名称,如 CREATE、MODIFY、DELETE 等。
- %T:事件发生的时间戳,格式为秒数自 Unix 纪元起的时间。
- %t:事件发生的时间戳,格式为 ISO 8601 格式的时间。
2.2、inotifywait 的编译安装
在服务器中执行以下操作, 在你的环境中,可能会缺少一些依赖的包, 使用以下方式安装它们: dnf -y install autoconf automake libtool
$ wget -c https://github.com/inotify-tools/inotify-tools/archive/refs/tags/3.21.9.6.tar.gz
$ tar -zvxf 3.21.9.6.tar.gz -C /usr/local/src/
$ cd /usr/local/src/inotify-tools-3.21.9.6/
$ ./autogen.sh && \
./configure --prefix=/usr/local/inotify-tools && \
make && \
make install
$ ls /usr/local/inotify-tools/bin/
inotifywait inotifywatch
对环境变量PATH进行追加,写入到配置文件中且让其永久生效。
# 执行
$ echo -e "\nPATH=\$PATH:/usr/local/inotify-tools/bin/" >> /etc/profile
# 立刻生效
$ . /etc/profile
2.3、功能实现
接下来就使用 inotifywait
来实现该功能。
监控的脚本如下:
#!/bin/bash
handler() {
line="$1"
dir=$(echo $line | awk -F '<@@@>' {'print $1'})
file=$(echo $line | awk -F '<@@@>' {'print $2'})
event=$(echo $line | awk -F '<@@@>' {'print $3'})
# 拼接完成的路径
file_path=$(echo "${dir}${file}")
# 判断时间差
timestamp=$(date +%s)
filetimestamp=`stat -c %Y "$file_path"`
timecha=$[$timestamp - $filetimestamp]
if [ $timecha -gt 30 ];then
# 调用更新修改时间的函数
touch -m "${file_path}"
echo "已更新文件 $file_path 的最后修改时间"
fi
}
# 监听文件,并开启多线程后台处理,否则将某些文件将不会处理完毕
inotifywait -m -r --format '%w<@@@>%f<@@@>%e' --event create,modify,move /hjne/data/ --exclude xinwei | while IFS=$'\n' read line; do
handler "$line" &
done
脚本的详解:
inotifywait -m -r --format '%w<@@@>%f<@@@>%e' --event create,modify,move /hjne/data/ --exclude xinwei | while IFS=$'\n' read line
inotifywait -m -r --event create,modify,move /hjne/data/ --exclude xinwei
表示递归监控 /hjne/data/ 目录下的所有文件(排除 xinwei 下的文件夹) 的创建、更新和移动的事件;inotifywait -m -r --format '%w<@@@>%f<@@@>%e' .....
表示文件的输出格式为:文件目录
+@@@
+事件发生的文件名(不含路径)
+@@@
+操作的事件
;(这样做的好处是防止文件名存在特殊字符导致操作异常,例如:文件名中出现空格)while IFS=$'\n' read line
如果同时操作多个文件时,这里按照换行符进行切割成单个文件的操作信息,然后进行遍历循环操作。handler "$line" &
将当前行数据作为参数调用handler
方法,并且后台运行。(经过测试发现如果没有后台运行同时操作多个文件时,有些文件可能会遗漏操作,这里排查了好久)
handler()
方法的详解$(echo $line | awk -F '<@@@>' {'print $1'})
表示截取字符串中的操作目录参数$(echo $line | awk -F '<@@@>' {'print $2'})
表示截取字符串中的操作文件名参数$(echo $line | awk -F '<@@@>' {'print $3'})
表示截取字符串中的事件参数if [ $timecha -gt 30 ];then
会判断文件的最后修改时间距离当前时间是否超过 30 秒,如果超过了 30 秒则会进行下面的操作,否则跳过;(这样做的目的是防止事件的循环依赖调用,例如:用户添加了文件,触发了create事件,然后你修改了文件的最后修改时间,此时会触发 modify 事件,你又修改了文件的最后修改时间,然后在触发 modify 事件 … )
三、使用 systemd 实现脚本的开机自启
现代Linux发行版通常使用 systemd 作为系统和服务管理器。
可以创建一个 systemd 服务单元来管理你的脚本,并实现开机自启动。
- 创建一个新的 systemd 服务单元文件,例如 watch-smb-files.service:
$ sudo vim /etc/systemd/system/watch-smb-files.service
- 在文件中定义服务单元的内容,例如:
[Unit]
Description=Service to handle directory creation event
[Service]
Type=simple
ExecStart=/etc/systemd/system/watch-smb-files.sh
[Install]
WantedBy=multi-user.target
- 启用服务以在系统启动时自动启动:
$ sudo systemctl enable watch-smb-files.service
- 启动服务:
$ sudo systemctl start watch-smb-files.service
- 查看特定服务单元的日志
# 查看 watch-smb-files.service 服务的日志
$ sudo journalctl -u watch-smb-files.service
# 查看服务最近的 50 条日志。
$ sudo journalctl -u watch-smb-files.service -n 50
# 实时查看日志
$ sudo journalctl -u watch-smb-files.service -f
# 使用 grep 进行过滤
$ sudo journalctl -u watch-smb-files.service | grep "Error"