Linux下检测USB文件系统变化:工具推荐及递归inotify示例代码
嘿,这几个问题其实都是围绕Linux下文件系统监控的核心场景,咱们一个个拆解清楚:
一、如何在USB设备文件系统变化时收到通知?
USB设备挂载后本质上就是系统里的一个普通目录,所以核心思路是监控它的挂载点目录。分两种情况处理:
- 手动监控已挂载的USB:如果你已经知道USB的挂载路径(比如
/media/yourusername/USB_DISK),直接用inotify工具或代码监听这个目录就行,和监控本地目录没区别。 - 自动监控USB挂载/卸载事件:如果要实现“USB一插上挂载就自动开始监控,拔掉就停止”,得结合
udev(Linux设备管理框架)+ inotify:- 写一个udev规则,捕获USB存储设备的挂载事件,触发脚本启动inotify监控;
- 比如在
/etc/udev/rules.d/99-usb-monitor.rules里添加:ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd*[0-9]", RUN+="/usr/local/bin/start-usb-monitor.sh %E{MOUNTPOINT}" ACTION=="remove", SUBSYSTEM=="block", KERNEL=="sd*[0-9]", RUN+="/usr/local/bin/stop-usb-monitor.sh" - 然后在
start-usb-monitor.sh里启动inotifywait或你的自定义监控程序,stop-usb-monitor.sh里杀掉对应进程即可。
二、如何递归使用inotify?
inotify本身是非递归的——它只能监听你指定的单个目录,不会自动处理子目录。要实现递归监控,有两种靠谱方式:
方式1:用现成工具的递归参数
最省事的是用inotifywait(inotify-tools包的一部分),它自带-r参数帮你实现递归监控:
inotifywait -m -r -e create,delete,modify /media/yourusername/USB_DISK
-m:持续监控(不是触发一次就退出)-r:递归监听所有子目录-e:指定要监听的事件类型(创建、删除、修改等)
方式2:手动实现递归逻辑(代码层面)
如果要自己写代码,核心逻辑是:
- 初始化阶段:遍历目标目录的所有子目录,给每个目录都调用
inotify_add_watch()添加监听; - 事件处理阶段:当收到
IN_CREATE事件时,判断新创建的对象是不是目录,如果是,立刻给这个新目录添加监听;如果收到IN_DELETE事件,inotify会自动移除对应目录的监听,不用手动处理。
三、有没有现成工具可以检测USB设备文件系统变化?
当然有,推荐几个常用的:
- inotifywait:刚才提过,inotify-tools包的核心工具,轻量且原生支持inotify,递归监控很方便。安装命令(Debian/Ubuntu):
sudo apt install inotify-tools - fswatch:跨平台的文件系统监控工具,支持递归,语法更简洁,安装后直接
fswatch /media/yourusername/USB_DISK就能监控所有变化。 - udev + inotifywait:前面说的组合方案,能实现USB设备的自动监控,适合需要无人值守的场景。
四、递归使用inotify的C语言示例代码
这里给一个完整的C代码示例,实现递归监控指定目录(比如USB挂载点)的文件/目录变化,自动处理新创建的子目录:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/inotify.h> #include <dirent.h> #include <errno.h> #define EVENT_SIZE (sizeof(struct inotify_event)) #define BUF_LEN (1024 * (EVENT_SIZE + 16)) // 递归为目录及其所有子目录添加inotify监听 static int add_watch_recursive(int inotify_fd, const char *dir_path) { DIR *dir; struct dirent *entry; char full_path[PATH_MAX]; // 为当前目录添加监听 if (inotify_add_watch(inotify_fd, dir_path, IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVE) == -1) { perror("Failed to add watch for directory"); return -1; } printf("Now watching: %s\n", dir_path); // 遍历子目录,递归添加监听 dir = opendir(dir_path); if (!dir) { perror("Failed to open directory"); return -1; } while ((entry = readdir(dir)) != NULL) { // 跳过当前目录和上级目录的占位符 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name); // 如果是子目录,递归处理 if (entry->d_type == DT_DIR) { if (add_watch_recursive(inotify_fd, full_path) == -1) { closedir(dir); return -1; } } } closedir(dir); return 0; } int main(int argc, char *argv[]) { int inotify_fd; ssize_t bytes_read; char buffer[BUF_LEN]; struct inotify_event *event; if (argc != 2) { fprintf(stderr, "Usage: %s <target-directory>\n", argv[0]); exit(EXIT_FAILURE); } // 创建inotify实例 inotify_fd = inotify_init(); if (inotify_fd == -1) { perror("Failed to initialize inotify"); exit(EXIT_FAILURE); } // 递归添加所有目录的监听 if (add_watch_recursive(inotify_fd, argv[1]) == -1) { close(inotify_fd); exit(EXIT_FAILURE); } printf("Recursively monitoring %s for changes...\n", argv[1]); // 持续读取inotify事件 while (1) { bytes_read = read(inotify_fd, buffer, BUF_LEN); if (bytes_read == -1) { perror("Failed to read inotify events"); break; } // 遍历所有收到的事件 for (char *ptr = buffer; ptr < buffer + bytes_read; ptr += EVENT_SIZE + event->len) { event = (struct inotify_event *)ptr; if (event->mask & IN_CREATE) { char full_path[PATH_MAX]; snprintf(full_path, sizeof(full_path), "%s/%s", argv[1], event->name); printf("[CREATED] %s\n", full_path); // 如果是新创建的目录,添加监听 if (event->mask & IN_ISDIR) { add_watch_recursive(inotify_fd, full_path); } } else if (event->mask & IN_DELETE) { char full_path[PATH_MAX]; snprintf(full_path, sizeof(full_path), "%s/%s", argv[1], event->name); printf("[DELETED] %s\n", full_path); } else if (event->mask & IN_MODIFY) { char full_path[PATH_MAX]; snprintf(full_path, sizeof(full_path), "%s/%s", argv[1], event->name); printf("[MODIFIED] %s\n", full_path); } else if (event->mask & IN_MOVED_FROM) { char full_path[PATH_MAX]; snprintf(full_path, sizeof(full_path), "%s/%s", argv[1], event->name); printf("[MOVED OUT] %s\n", full_path); } else if (event->mask & IN_MOVED_TO) { char full_path[PATH_MAX]; snprintf(full_path, sizeof(full_path), "%s/%s", argv[1], event->name); printf("[MOVED IN] %s\n", full_path); // 如果是移动进来的目录,添加监听 if (event->mask & IN_ISDIR) { add_watch_recursive(inotify_fd, full_path); } } } } close(inotify_fd); exit(EXIT_SUCCESS); }
编译和运行方式:
gcc -o recursive_inotify_watch recursive_inotify_watch.c ./recursive_inotify_watch /media/yourusername/USB_DISK
代码说明:
- 监听了创建、删除、修改、移动四类常见事件,你可以根据需求调整
inotify_add_watch的事件掩码; - 自动处理新创建的子目录,递归添加监听;
- 当目录被删除时,inotify会自动移除对应watch,无需手动处理;
- 代码跳过了
.和..目录,避免无限递归。
内容的提问来源于stack exchange,提问作者user7163017




