如何正经的实现shell脚本单例运行

54次阅读
没有评论

共计 2339 个字符,预计需要花费 6 分钟才能阅读完成。

这篇文章将为大家详细讲解有关如何正经的实现 shell 脚本单例运行,丸趣 TV 小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

看起来可行的方法

一个非常简单的思路就是,新的脚本被执行时,先检测当前脚本是否有其他实例正在运行,如果有则直接退出。

runCount=$(ps -ef|grep test.sh | grep -v grep -c) if [  ${runCount}  -ge 1 ] then echo -e  test.sh already running,num:${runCount}  exit 1; fi while true do echo  test.sh run  sleep 1 done

这里通过 ps 获取到当前在运行的 test.sh 脚本数,如果大于 1,说明已经有在运行的了。

但是你运行会发现,其程序数量不只是一个。

$ ./test.sh test.sh already running,num:2

惊不惊喜? 为什么为这样呢? 原因在于,shell 脚本中一个命令执行相当于 fork 了一个进程执行,这里执行的是查找 tesh.sh 并 grep 的程序,另外还有一个就是当前运行的脚本程序,这样的方式自然就会出现每次都有两个了。

当然判断条件这里你可以换一下,例如数量大于 2,但终归不太好。

文件锁

实际上这种方法你已经在《如何让你的程序同时只有一个在运行》介绍过了,只不过之前是用于编写 C /C++ 程序,而这里是用于 shell 脚本。

我们来回顾一下,这是一个怎样的过程:

运行前检查是否有该锁文件,并且文件中的进程正在运行

如果有并且程序正在运行,则已经有实例在运行

否则,无实例,创建锁文件,写入进程 id

退出时,删除锁文件

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

#!/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}

我们试着运行其中一个,然后另外一个窗口尝试运行:

$ ./test.sh ./test.sh already running

由于已经有实例在运行,发现新的程序无法运行了。而等旧的脚本运行完之后,新的就可以运行了。

实际上这里面有几个点非常巧妙:

kill -0 `cat \${LOCKFILE}` 这里用于检测该进程是否存在,避免进程不在了,但是锁文件还在,导致后面的脚本无法运行。

trap rm -f \${LOCKFILE}; exit INT TERM EXIT 用于确保脚本退出时,锁文件会被删除。

rm -f {LOCKFILE} 脚本最后需要删除锁文件

flock

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

脚本被意外中断,没来得及执行删除

多个脚本产生竞争,导致判断异常,比如前面有一个脚本运行,判断没有锁文件,下一步准备创建,但是另外一个脚本又先创建了,就会导致异常了。

因此我们可以考虑使用 flock:

#!/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 失败了,我们这里可以退出

而即使锁定了,脚本退出后,也会自动释放

因此这里避免了锁没有释放的情况。

另一种做法

查看 flock 的 man 手册,我们发现它还有一个例子是这么做的:

[  ${FLOCKER}  !=  $0  ]   exec env FLOCKER= $0  flock -en  $0   $0   $@  || :

在脚本开头加上上面这么一行就可以了。例如:

#!/usr/bin/env bash [  ${FLOCKER}  !=  $0  ]   exec env FLOCKER= $0  flock -en  $0   $0   $@  || : # 脚本要做的其他事情  sleep 1024

解释一下:如果 ${FLOCKER} 环境变量没有设置,则尝试将脚本本身加锁,如果加锁成功,则运行当前脚本,(并且带上原有的参数),否则的话静默退出。

总结

单例运行本身思路是很简单的,就是探测当前是否有实例在运行,如果有,则退出,但是这里如何判断,却并不是那么容易。

最后,总结一下本文出现的一些该掌握的信息:

$0 脚本名称

$@ 脚本参数

$$ 当前脚本进程 id

$? 上一条命令执行结果

描述符 0 标准输入

描述符 1 标准输出

描述符 2 标准错误

重定向

关于“如何正经的实现 shell 脚本单例运行”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-25发表,共计2339字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)