怎么正经的实现shell脚本单例运行?

假设你的一个脚本已经在运行了,如果避免再次被执行呢?也就是如何实现单例运行? 2020-05-26 10:28:36 shell脚本单例运行 为什么 Linux 系统调用会消耗较多资源 当我们在编写应用程序时,系统调用并不是一个离我们很远的概念,一个简单的 Hello World 会在执行时触发几十次系统调用,而在线上出现性能问题时,可能也需要我们与系统调用打交道。

假设你的一个脚本已经在运行了,如果避免再次被执行呢?也就是如何实现单例运行?

假设你的一个脚本已经在运行了,如果避免再次被执行呢?也就是如何实现单例运行?

[[327604]]

看起来可行的方法

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

  1. runCount=$(ps-ef|greptest.sh|grep-vgrep-c)
  2. if["${runCount}"-ge1]
  3. then
  4. echo-e"test.shalreadyrunning,num:${runCount}"
  5. exit1;
  6. fi
  7. whiletrue
  8. do
  9. echo"test.shrun"
  10. sleep1
  11. done

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

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

  1. $./test.sh
  2. test.shalreadyrunning,num:2

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

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

文件锁

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

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

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

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

  1. #!/usr/bin/envbash
  2. LOCKFILE=/tmp/test.lock
  3. if[-e${LOCKFILE}]&&kill-0`cat${LOCKFILE}`;then
  4. echo"$0alreadyrunning"
  5. exit
  6. fi
  7. #确保退出时,锁文件被删除
  8. trap"rm-f${LOCKFILE};exit"INTTERMEXIT
  9. #将当前程序进程id写入锁文件
  10. echo$$>${LOCKFILE}
  11. #做你需要的事情
  12. sleep1000
  13. #删除锁文件
  14. rm-f${LOCKFILE}

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

  1. $./test.sh
  2. ./test.shalreadyrunning

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

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

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

flock

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

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

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

  1. #!/usr/bin/envbash
  2. LOCK_FILE=/tmp/test.lock
  3. exec99>"$LOCK_FILE"
  4. flock-n99
  5. if["$?"!=0];then
  6. echo"$0alreadyrunning"
  7. exit1
  8. fi
  9. #脚本要做的其他事情
  10. sleep1024

解释一下:

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

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

另一种做法

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

  1. ["${FLOCKER}"!="$0"]&&execenvFLOCKER="$0"flock-en"$0""$0""$@"||:

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

  1. #!/usr/bin/envbash
  2. ["${FLOCKER}"!="$0"]&&execenvFLOCKER="$0"flock-en"$0""$0""$@"||:
  3. #脚本要做的其他事情
  4. sleep1024

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

总结

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

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

  • $0 脚本名称
  • $@ 脚本参数
  • $$ 当前脚本进程id
  • $? 上一条命令执行结果
  • 描述符0 标准输入
  • 描述符1 标准输出
  • 描述符2 标准错误
  • > 重定向

©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经

(0)
打赏 微信扫码打赏 微信扫码打赏 支付宝扫码打赏 支付宝扫码打赏
清一色的头像清一色管理团队
上一篇 2023年5月6日 09:59
下一篇 2023年5月6日 09:59

相关推荐

发表评论

登录后才能评论

联系我们

在线咨询:1643011589-QQbutton

手机:13798586780

QQ/微信:1074760229

QQ群:551893940

工作时间:工作日9:00-18:00,节假日休息

关注微信