作者:守望,linux应用开发者,目前在公众号【编程明珠】上分享Linux/C/C++/数据结构与算法/工具等原创技术文章和学习资源。假设你的一个脚本已经在运行,如何避免它再次被执行?即如何实现单例操作?一个很简单看似可行的想法是,当一个新的脚本执行时,首先检查当前脚本是否有Otherinstancesarerunning,如果有则直接退出。#!/usr/bin/envbash#test.sh来源:公众号编程珠#获取当前运行的test.sh脚本数量runCount=$(ps-ef|greptest.sh|grep-vgrep-c)if["${runCount}"-ge1]thenecho-e"test.shalreadyrunning,num:${runCount}"exit1;fiwhiletruedoecho"test.shrun"sleep1done这里通过ps获取当前运行的test.sh脚本数量,如果它大于1,表明它已经在运行。但是当你运行它的时候,你会发现程序的数量不只是一个。$./test.shtest.shalreadyrunning,num:2惊不惊喜?为什么是这样?原因是在shell脚本中执行一条命令相当于执行了一个fork进程,而这里的执行是搜索tesh.sh并grep该程序,还有一个是当前正在运行的脚本程序,所以每次自然会有两个。当然你可以改变这里的判断条件,比如数字大于2,但毕竟不是很好。其实你在《如何让你的程序同时只有一个在运行》里已经介绍过这个方法,只不过之前是用来写C/C++程序的,这里是用来做shell脚本的。我们回顾一下,这是一个什么样的进程:1.运行前检查是否有锁文件,文件中的进程是否正在运行2.如果有并且程序正在运行,说明已经有一个实例在运行3.否则,no例子,创建一个锁文件,写入进程id4.退出时,删除锁文件解释第一项,为什么一定要确保锁文件中的进程正在运行,因为,在某些情况下,文件是不如果它在运行时退出,则删除,导致新实例永远不会运行。#!/usr/bin/envbash#来源:公众号编程珠LOCKFILE=/tmp/test.lockif[-e${LOCKFILE}]&&kill-0`cat${LOCKFILE}`;thenecho"$0alreadyrunning"exitfi#确保退出时锁文件被删除trap"rm-f${LOCKFILE};exit"INTTERMEXIT#将当前程序进程id写入锁文件echo$$>${LOCKFILE}#Dowhatyouneedsleep1000#Deletethelockfilerm-f${LOCKFILE}我们尝试运行其中一个,然后尝试运行另一个窗口:$./test.sh./test.shalreadyrunning由于已经有实例在运行,我们发现新程序不能跑步。并且在旧脚本运行完之后,新脚本就可以运行了。其实这里有几点很巧妙:kill-0`cat\${LOCKFILE}`这个用来检测进程是否存在,防止进程消失,但是锁文件还在,导致后续脚本无法运行。trap"rm-f\${LOCKFILE};exit"INTTERMEXIT用于确保在脚本退出时删除锁文件。rm-f{LOCKFILE}脚本最后需要删除锁文件flock说到锁文件,这里就不得不提到flock命令了。如果没有上面的一些巧妙处理,我们往往很难删除原来创建的锁文件。比如脚本意外中断,来不及执行删除多个脚本,导致竞争,导致判断异常。锁定文件已准备好在下一步创建,但首先创建了另一个脚本,这将导致异常。所以我们可以考虑使用flock:#!/usr/bin/envbash#来源:公众号编程明珠LOCK_FILE=/tmp/test.lockexec99>"$LOCK_FILE"flock-n99if["$?"!=0];thenecho"$0alreadyrunning"exit1fi#sleep1024脚本要做的其他事情说明:exec99>"$LOCK_FILE"的意思是创建文件描述符99,指向锁文件,为什么是99?110其实是可以的,只是为了兼容当前脚本打开的文件描述符冲突(比如与0,1,2冲突)。flock-n99尝试锁定文件描述符,操作系统保证原子性。一旦flock失败,我们就可以从这里退出。即使加锁了,脚本退出后也会自动释放,这样就避免了锁没有释放的情况。.另一种方式查看flock的man手册,我们发现还有另一个例子是这样做的:["${FLOCKER}"!="$0"]&&execenvFLOCKER="$0"flock-en"$0""$0""$@"||:只需在脚本开头添加上述行即可。例如:#!/usr/bin/envbash#来源:公众号编程珠玑["${FLOCKER}"!="$0"]&&execenvFLOCKER="$0"flock-en"$0""$0""$@"||:#脚本sleep1024要做的其他事情说明:如果没有设置${FLOCKER}环境变量,尝试锁定脚本本身,如果锁定成功,运行当前脚本(带原参数),否则,静默退出。总结一下单例运行的思想本身很简单,就是检测当前是否有实例在运行,如果有就退出,但是这里怎么判断就没那么容易了。最后总结一下本文应该掌握的一些信息$0脚本名$@脚本参数$$当前脚本进程id$?last命令执行结果描述符0标准输入描述符1标准输出描述符2标准错误>重定向本文转载自微信公众号《编程珠玑》,可通过以下二维码关注。转载本文请联系编程明珠公众号。
