Bash脚本检测进程运行状态、cron执行异常及flock报错排查
Bash单实例脚本问题排查与实现方案
各方案失效原因
- pgrep方案异常原因:cron调度脚本时,会先启动一个shell进程加载执行脚本,该父进程的命令行同样包含脚本路径,
pgrep -f $0会同时匹配到父shell进程、当前脚本进程两个PID,和SSH手动执行时的进程结构不一致,因此返回两个PID导致判断失效,才会出现14767 14770 . != . 14770的输出。 - ps统计方案异常原因:存在两个语法和逻辑错误:第一,判断逻辑写混,
[ -n "$(...)" ]是判断字符串非空,单中括号内的>是文件重定向符而非数值比较符,根本不会执行数值大小判断;第二,脚本内执行管道命令时,会生成包含脚本名的子进程,哪怕加了grep -v grep,统计到的进程数也会比手动执行时多1,阈值判断完全失准,因此始终触发"already running"提示。 - flock方案报错原因:cron默认的PATH环境变量不包含脚本所在目录,若cron任务里写的是相对路径调用脚本,
$0拿到的是不带完整路径的脚本名,flock无法定位到脚本文件,就会抛出flock: failed to execute run.sh: No such file or directory错误。
可靠的单实例实现方案
方案1:flock实现(推荐,无逻辑漏洞)
该方案依赖系统自带的flock文件锁工具,不需要手动匹配进程,不会出现误判:
#!/bin/bash # 提前获取脚本绝对路径,解决cron下相对路径找不到文件的问题 SCRIPT_ABSPATH=$(readlink -f "$0") # 独立锁文件路径,避免锁脚本本身带来的权限问题 LOCK_FILE="/var/run/$(basename "$0").lock" # 绑定文件描述符到锁文件,非阻塞申请锁 exec 9>"$LOCK_FILE" if ! flock -n 9; then echo "Task is already running, exit now." exit 1 fi # 以下为正常业务逻辑 echo "Script started, PID: $$" # 实际业务命令写在这里
关键说明:
- 用
readlink -f提前解析脚本绝对路径,彻底规避cron下相对路径的问题 - 单独创建锁文件而非直接锁定脚本自身,避免脚本无执行权限、目录只读导致的异常
flock -n为非阻塞模式,拿不到锁直接退出,不会卡住等待上一个任务执行- 脚本退出时操作系统会自动释放文件锁,不需要手动清理锁文件
方案2:PID文件实现(无flock依赖时使用)
若系统未预装flock,可使用PID文件方案,需要额外处理异常退出残留的PID文件:
#!/bin/bash PID_FILE="/var/run/$(basename "$0").pid" # 校验已存在的PID文件 if [ -f "$PID_FILE" ]; then OLD_PID=$(cat "$PID_FILE") # 发送空信号校验旧进程是否真实存在,避免残留PID文件导致误判 if kill -0 "$OLD_PID" 2>/dev/null; then echo "Task is already running, PID: $OLD_PID, exit now." exit 1 else # 清理无效残留PID文件 rm -f "$PID_FILE" fi fi # 写入当前进程PID,脚本退出时自动清理PID文件 echo $$ > "$PID_FILE" trap "rm -f $PID_FILE" EXIT # 以下为正常业务逻辑 echo "Script started, PID: $$" # 实际业务命令写在这里
内容的提问来源于stack exchange,提问作者Andrew Newby
相关产品推荐
相关产品推荐

