在折腾 NAS、家庭服务器或 Linux 运维时,我们经常需要让自定义脚本在“特定文件或目录就绪”后才执行。Systemd 提供了强大的依赖管理能力,但在复杂的存储环境中,如果不了解底层的触发机制和挂载时序,很容易掉进脚本无法唤醒的“坑”里。
一、基于事件驱动的 .path 方案
当你需要脚本在特定文件或文件夹出现、变动时才执行,Systemd 的 .path 单元是一个精准的事件驱动工具。它底层依赖 Linux 内核的 inotify 机制,当目标路径发生创建或修改时,内核主动发送通知,触发关联的 .service 启动。
常用配置指令
你可以根据具体需求,在 .path 文件的 [Path] 区块中使用以下指令:
- PathExists=:指定路径(文件或目录)存在时触发。
- PathChanged=:指定文件被修改并关闭后触发。
- DirectoryNotEmpty=:指定目录存在且非空时触发。
配置示例
1. 编写 .path 文件(如 custom-script.path)
[Unit]
Description=Monitor target directory for readiness
[Path]
PathExists=/mnt/data/scripts
[Install]
WantedBy=multi-user.target2. 编写同名的 .service 文件(如 custom-script.service)
[Unit]
Description=Execute script after target directory is ready
[Service]
Type=oneshot
ExecStart=/mnt/data/scripts/your_script.sh避坑警告: 被
.path触发的.service文件通常不需要也不应该包含[Install]区块。启用时,仅需执行systemctl enable custom-script.path即可。如果误执行了enable service,系统开机时会无视.path的状态直接启动脚本,导致报错。
二、原生优雅的最优解:RequiresMountsFor 方案
如果你的核心痛点仅仅是“等待 NAS 或数据盘挂载完成”,那么请直接放弃复杂的 .path 监听。Systemd 原生提供了一个专门应对此场景的指令:RequiresMountsFor=。
这是最符合 Linux 规范的优雅做法。Systemd 会自动解析系统的挂载树(包括 fstab 或 .mount 单元),在指定的挂载点真正挂载成功前,它会严格阻塞当前服务的启动。
配置示例
直接在一个普通的 .service 文件中声明绝对依赖:
[Unit]
Description=Execute script after NAS mount is ready
After=network-online.target
Wants=network-online.target
# 核心:要求该路径的所有相关挂载点必须就绪,才启动此服务
RequiresMountsFor=/mnt/data/scripts
[Service]
Type=simple
ExecStart=/mnt/data/scripts/your_script.sh
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target优势: 配置极简,无需编写任何额外的路径检测逻辑,将复杂的挂载时序管理交还给了 Systemd 本身。
三、非标准挂载的“覆盖陷阱”与轮询兜底方案
在 NAS 或带有额外数据盘的服务器环境中,标准的 .path 方案极易失效,其核心元凶就是**“挂载覆盖陷阱”**。
什么是挂载覆盖陷阱?
- Systemd 启动时优先检测目标路径。此时挂载尚未完成,Systemd 会将 inotify 监听绑定到当前 RootFS 底层空目录的 inode(文件索引节点)上。
- 后续存储阵列完成挂载,以新的文件系统覆盖了原有的底层目录。 结果: inotify 监听的依然是原底层目录的 inode(现已被新挂载点隐藏),无法自动关联到新文件系统,监听彻底失效。此时,前两种方案都会失效。
解决方案:主动轮询
面对这种“非标准挂载”,最有效的方法是放弃被动监听,在 .service 中通过 ExecStartPre 添加阻塞式的主动轮询检测。
[Unit]
Description=Execute script with active polling (avoid mount overlay trap)
After=network-online.target
[Service]
Type=simple
# 核心:主动轮询检测。每 5 秒查询一次,路径存在且就绪后再往下执行
ExecStartPre=/bin/bash -c 'while [ ! -d /mnt/data/scripts ]; do sleep 5; done'
ExecStart=/mnt/data/scripts/your_script.sh
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target解析: ExecStartPre 中的 while 循环会一直卡住该服务的启动流程,直到目标目录真正存在。虽然这是个“笨办法”,但它彻底规避了 inode 绑定失效的陷阱,是处理复杂第三方挂载环境的最强兜底利器。