← 中文资源

简体中文版本

Bash flock 实现单实例任务

通过文件描述符 9 加 flock 排他锁,限制等待时间,获取失败以 75 退出,并确保退出时释放锁。

阅读英文原文

功能说明

脚本为定时备份类任务提供单实例保护。它打开锁文件并绑定到文件描述符 9,用 flock -w 在限定秒数内尝试排他锁;失败说明已有实例在运行,以状态码 75(EX_TEMPFAIL)退出。成功时打印持锁进程号,创建临时工作文件并模拟任务,退出时由 trap 解锁并删除工作文件。锁随进程结束自动释放,不会留下需要手工清理的锁状态。

关键代码

exec 9>“$lock_file” 同时创建并打开文件,后续 flock 作用于该描述符而不是路径,避免路径被替换导致锁错对象。trap cleanup EXIT 在正常或异常退出时执行,先删除工作文件,再 flock -u 9 主动解锁。cleanup 记录进入时的 $? 并用 exit 保留原始退出码。锁等待时间来自 LOCK_WAIT_SECONDS,任务时长可用 JOB_SECONDS 调整。

使用注意

flock 是 advisory lock,只有同样使用 flock 的进程才会遵守,不能阻止任意进程写同一文件。NFS 等网络文件系统上的锁语义依实现而异,跨主机任务应确认支持。锁文件路径应固定且所有实例一致,放在 /tmp 时要注意重启清理与权限。退出码 75 便于 cron 或调度器识别“本次跳过”,不要当成成功。容器中 PID 命名空间不同,锁文件需共享卷才有效。

验证方式

同时启动两个实例:第一个立即获得锁并运行,第二个在等待超时后输出 could not acquire 并以 75 退出。把 LOCK_WAIT_SECONDS 设大,第二个会等第一个结束后成功。任务结束后确认临时工作文件已删除;用 flock 或 lsof 检查锁文件不再被持有。可人为 kill 第一个进程,验证锁自动释放。

代码示例

#!/usr/bin/env bash
set -euo pipefail

lock_file=${1:-"${TMPDIR:-/tmp}/devexamples-backup.lock"}
max_wait=${LOCK_WAIT_SECONDS:-5}
work_file=""

cleanup() {
    local status=$?
    if [[ -n "$work_file" && -f "$work_file" ]]; then
        rm -f -- "$work_file"
    fi
    flock -u 9 2>/dev/null || true
    exit "$status"
}
trap cleanup EXIT

exec 9>"$lock_file"

if ! flock -w "$max_wait" 9; then
    printf 'could not acquire %s within %ss\n' "$lock_file" "$max_wait" >&2
    exit 75
fi

printf 'lock acquired by pid %d\n' "$"

work_file="$(mktemp "${TMPDIR:-/tmp}/backup.XXXXXX")"
printf 'job %d started at %s\n' "$" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$work_file"

sleep "${JOB_SECONDS:-1}"
cat -- "$work_file"
printf 'job %d finished; lock released on exit\n' "$"