文章

如何保证shell脚本单例执行

如何保证shell脚本单例执行

文件锁

思路

  1. 运行前检查是否有该锁文件,并且文件中的进程正在运行
  2. 如果有并且程序正在运行,则已经有实例在运行
  3. 否则,无实例,创建锁文件,写入进程 id
  4. 退出时,删除锁文件

    解释一下第一条,为什么一定要确定锁文件中的进程正在运行,因为,有些情况下如果运行的时候退出没有删除该文件,则会导致新的实例永远无法运行

脚本代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env bash

LOCKFILE=/tmp/test.lock
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
    echo " $0 already running"
    exit
fi

# 确保退出时,锁文件被删除
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
#将当前程序进程id写入锁文件
echo $$ > ${LOCKFILE}

# 做你需要的事情
sleep 1000

# 删除锁文件
rm -f ${LOCKFILE}

巧妙点:

  • kill -0 cat \${LOCKFILE} 这里用于检测该进程是否存在,避免进程不在了,但是锁文件还在,导致后面的脚本无法运行
  • trap “rm -f ${LOCKFILE}; exit” INT TERM EXIT 用于确保脚本退出时,锁文件会被删除
  • rm -f {LOCKFILE} 脚本最后需要删除锁文件

flock

说到锁文件,这里就不得不提 flock 命令了。没有前面的一些巧妙处理,我们很多时候会很难删除原先创建的锁文件,比如

  • 脚本被意外中断,没来得及执行删除
  • 多个脚本产生竞争,导致判断异常,比如前面有一个脚本运行,判断没有锁文件,下一步准备创建,但是另外一个脚本又先创建了,就会导致异常了 因此我们可以考虑使用 flock
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    #!/usr/bin/env bash
    LOCK_FILE=/tmp/test.lock
    exec 99>"$LOCK_FILE"
    flock -n 99
    if [ "$?" != 0 ]; then
      echo "$0 already running"
      exit 1
    fi
    #脚本要做的其他事情
    sleep 1024
    

    解释一下:

  • exec 99>”$LOCK_FILE” 表示创建文件描述符 99,指向锁文件,为何是 99?110 其实也是可以的,只是为了和当前脚本可能打开的文件描述符冲突(例如和 0,1,2 冲突)
  • flock -n 99 尝试对该文件描述符加锁,由操作系统保证原子性
  • 一旦 flock 失败了,我们这里可以退出
  • 而即使锁定了,脚本退出后,也会自动释放 因此这里避免了锁没有释放的情况

Ø 注意: - 父进程fork()子进程,子进程继承父进程的锁

本文由作者按照 CC BY 4.0 进行授权

热门标签