这是现在基本上所有电脑都能实现的功能了,无非就是后台自动搜索设备,然后自动挂载。可能这个场景在没有GUI版的Linux用处不大,导致每次插入U盘都得手动挂载一遍,于是决定写个脚本实现自动挂载。

我们要实现的效果就是,插入U盘,系统自动把U盘挂载在/path-to-mount/label路径下,label是U盘的名称(注意不要插入两张Label名字相同的SD卡)。

自动挂载

  • 查看SD卡在miniPC的挂载路径

    lsblk
    

    可以得到

    NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
    sda      8:0    0 476.9G  0 disk
    ├─sda1   8:1    0   512M  0 part /boot/efi
    ├─sda2   8:2    0 475.5G  0 part /
    └─sda3   8:3    0   976M  0 part [SWAP]
    sdb      8:16   1  58.9G  0 disk
    └─sdb1   8:17   1  58.9G  0 part
    sdc      8:32   1     0B  0 disk
    

    可以看到sdb1就是我的SD卡,但是目前还没有挂载

  • sdb1为一个文件,他的位置在/dev/sdb1,我们找到这个SD卡的UUID

    sudo blkid /dev/sdb1
    /dev/sdb1: LABEL="Nikon-1" UUID="4A21-0000" BLOCK_SIZE="512" TYPE="exfat" PTTYPE="dos"
    

    其中LABEL就是你给U盘的命名,插入U盘后电脑显示的U盘的名字。

  • 创建脚本/usr/local/bin/auto-mount-usb.sh

    #!/usr/bin/env bash
    
    logfile="/var/log/udev-usb-label.log"
    mount_base="/base-path-to-mount"
    
    exec >> "$logfile" 2>&1
    logger "Auto-mount script triggered: $@"
    
    LABEL="$1"
    [[ -z "$LABEL" ]] && {
        logger "No LABEL provided"
        exit 1
    }
    
    DEVICE=$(readlink -f "/dev/disk/by-label/$LABEL")
    if [[ ! -b "$DEVICE" ]]; then
        logger "Device for label $LABEL not found"
        exit 1
    fi
    
    MOUNTPOINT="$mount_base/$LABEL"
    
    mkdir -p "$MOUNTPOINT"
    
    logger "Mounting $DEVICE to $MOUNTPOINT"
    if /usr/bin/mount -t auto -o uid=1000,gid=1000,umask=0022,iocharset=utf8 "$DEVICE" "$MOUNTPOINT"; then
        logger "Mounted $LABEL to $MOUNTPOINT successfully"
    else
        logger "Failed to mount $LABEL"
        exit 1
    fi
    
    • Log files放在了/tmp,重启后会自动清理
    • 需要修改一下挂载点mount_base="/base-path-to-mount",假设我都放在/home/username/,每插入U盘,就会在这个文件夹下以U盘的Label为名字创建新的文件夹,注意不要插入两张Label相同的SD卡
  • 刷新脚本权限

    sudo chmod +x ./auto-mount-usb.sh
    
  • 创建systemd服务/etc/systemd/system/[email protected]

    [Unit]
    Description=Auto Mount USB LABEL=%i
    After=local-fs.target
    
    [Service]
    Type=oneshot
    ExecStart=/usr/local/bin/auto-mount-usb.sh %i
    
  • 新建规则文件/etc/udev/rules.d/99-auto-usb-mount.rules

    ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_LABEL}!="", ENV{SYSTEMD_WANTS}+="auto-mount-usb@%E{ID_FS_LABEL}.service"
    
  • 重新加载

    sudo udevadm control --reload-rules
    sudo udevadm trigger
    sudo systemctl daemon-reload
    

这样当你在Linux中新插入U盘后,就会在特定的位置新创建一个文件夹,然后访问你U盘的文件了

卸载U盘

当希望拔出U盘时,一定要先卸载U盘,否则有可能造成文件损坏,我们这里还是先使用手动的卸载方法,输入命令

sudo umount /path_to_disk

如果什么也不返回,就可以安全地插拔U盘了,如果挂载点被占用,可以查看被谁占用(注意如果终端在U盘路径内也会卸载不成功):

sudo lsof +D /path_to_disk

卸载之后在本地的文件夹不会删除,变成了普通的文件夹,如果需要可以手动删除。

Raw文件

挂载U盘 / SD卡很大原因是为了配合File Browser自建文件浏览器,方便传送文件给朋友,比如我把相机插到miniPC上,朋友就可以访问和下载相机SD卡的照片。我的相机是Nikon Zfc,它可以配置拍一次照片导出一张RAW文件(.NEF)用于精修,一张.jpg用于预览。但是File Browser是无法在线预览RAW文件的。所以我们可以在/usr/local/bin/auto-mount-usb.sh脚本里添加一步,比如插入名为nikon-x的SD卡后自动把RAW文件挪到一个新建的子文件夹中,然后在File Browser里就可以分类显示.jpg.NEF了。

# ---------------- Nikon 文件分类逻辑 ----------------
if [[ "$LABEL" == nikon-* ]]; then
    logger "Detected Nikon device. Scanning for image files..."

    TARGET_DIR="$MOUNTPOINT/DCIM/102NZ_FC"
    RAW_DIR="$TARGET_DIR/RAW"
    JPG_DIR="$TARGET_DIR/JPG"

    if [[ -d "$TARGET_DIR" ]]; then
        mkdir -p "$RAW_DIR" "$JPG_DIR"

        # 移动 RAW 文件 (.NEF)
        find "$TARGET_DIR" -maxdepth 1 -type f -iname "*.nef" -exec mv {} "$RAW_DIR" \;

        # 移动 JPG 文件
        find "$TARGET_DIR" -maxdepth 1 -type f -iname "*.jpg" -exec mv {} "$JPG_DIR" \;

        logger "Finished organizing files into RAW and JPG folders"
    else
        logger "Target directory $TARGET_DIR not found. Skipping file classification."
    fi
fi

LRF文件

同样大疆在拍摄视频之后会出现一个LRF文件和一个MP4文件,我们可以加入这条命令让其自动区分

# ---------------- DJI 文件分类逻辑 ----------------
if [[ "$LABEL" =~ ^dji-[0-9]+$ ]]; then
    logger "Detected DJI device. Scanning for video and LRF files..."

    TARGET_DIR="$MOUNTPOINT/DCIM/DJI_001"
    LRF_DIR="$TARGET_DIR/LRF"
    MP4_DIR="$TARGET_DIR/MP4"

    if [[ -d "$TARGET_DIR" ]]; then
        mkdir -p "$LRF_DIR" "$MP4_DIR"

        # 移动 .LRF 文件
        find "$TARGET_DIR" -maxdepth 1 -type f -iname "*.lrf" -exec mv {} "$LRF_DIR" \;

        # 移动 .MP4 文件
        find "$TARGET_DIR" -maxdepth 1 -type f -iname "*.mp4" -exec mv {} "$MP4_DIR" \;

        logger "Finished organizing DJI files into LRF and MP4 folders"
    else
        logger "Target directory $TARGET_DIR not found. Skipping file classification."
    fi
fi

注意事项

  • 配合File Browser的Docker容器使用时,需要先挂载USB,然后再运行Docker容器,否则会找不到文件,如果容器已经运行,可以down再up重启一遍
  • 这样脚本在插入USB后就会马上对文件进行分类,建议先做备份,然后再插入miniPC,以免造成文件丢失
  • 假设有一部分未分类,有一部分已分类,程序会只把未分类的归类,反复插拔U盘不会造成影响
  • 如果有相同名称的有可能会造成文件丢失,但是Nikon会按照快门数给照片分配ID,因此问题不大

udevil

相比于自己写脚本,也可以使用udevil

  • 安装

    sudo apt update
    sudo apt install udevil
    
  • 修改配置文件/etc/udevil/udevil.conf

    allowed_media_dirs = /media/$USER, /run/media/$USER
    

    可以把默认路径写在这里,越往前优先级越高,放在第一位就是默认路径

  • 创建服务文件

    sudo nano /etc/systemd/system/devmon.service
    
    [Unit]
    Description=Devmon Auto-mount Service
    After=network.target
    
    [Service]
    Type=simple
    User=<your_username>
    Group=<your_username>
    ExecStart=/usr/bin/devmon
    Restart=on-failure
    RestartSec=5
    
    [Install]
    WantedBy=multi-user.target
    
  • 重新加载配置

    sudo systemctl daemon-reload
    sudo systemctl restart devmon