功能说明
脚本为定时备份类任务提供单实例保护。它打开锁文件并绑定到文件描述符 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' "$"