You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Linux下检测USB文件系统变化:工具推荐及递归inotify示例代码

嘿,这几个问题其实都是围绕Linux下文件系统监控的核心场景,咱们一个个拆解清楚:

一、如何在USB设备文件系统变化时收到通知?

USB设备挂载后本质上就是系统里的一个普通目录,所以核心思路是监控它的挂载点目录。分两种情况处理:

  1. 手动监控已挂载的USB:如果你已经知道USB的挂载路径(比如/media/yourusername/USB_DISK),直接用inotify工具或代码监听这个目录就行,和监控本地目录没区别。
  2. 自动监控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:手动实现递归逻辑(代码层面)

如果要自己写代码,核心逻辑是:

  1. 初始化阶段:遍历目标目录的所有子目录,给每个目录都调用inotify_add_watch()添加监听;
  2. 事件处理阶段:当收到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

火山引擎 最新活动