假设你的一个脚本已经在运行,如何避免它再次被执行?即如何实现单例操作?一个很简单看似可行的想法是,当一个新的脚本被执行时,首先检测当前是否有该脚本的其他实例在运行,如果有则直接退出。runCount=$(ps-ef|greptest.sh|grep-vgrep-c)if["${runCount}"-ge1]thenecho-e"test.shalreadyrunning,num:${runCount}"exit1;fiwhiletruedoecho"测试。shrun"sleep1done这里通过ps获取当前运行的test.sh脚本的个数。如果它大于1,则表示它已经在运行。但是当你运行它的时候,你会发现程序的数量不只是一个。$./test.shtest.shalreadyrunning,num:2惊不惊喜?为什么是这样?原因是在shell脚本中执行一条命令相当于执行了一个fork进程,而这里的执行是搜索tesh.sh并grep该程序,还有一个是当前正在运行的脚本程序,所以每次自然会有两个。当然你可以改变这里的判断条件,比如数字大于2,但毕竟不是很好。其实你在《如何让你的程序同时只有一个在运行》里已经介绍过这个方法,只是之前写C/C++程序用的,这里是shell脚本用的。我们先回顾一下进程是什么:运行前查看是否有锁文件,文件中的进程是否在运行。如果有并且程序正在运行,则实例已经在运行。否则,没有实例,并创建锁文件。写入进程id退出时,删除锁文件。解释一下第一项,为什么一定要确保lock文件中的进程正在运行,因为在某些情况下,如果在运行时不删除文件就退出,会导致新的实例永远无法运行。#!/usr/bin/envbashLOCKFILE=/tmp/test.lockif[-e${LOCKFILE}]&&kill-0`cat${LOCKFILE}`;thenecho"$0alreadyrunning"exitfi#确保退出时删除锁文件trap"rm-f${LOCKFILE};exit"INTTERMEXIT#将当前程序进程id写入锁文件echo$$>${LOCKFILE}#做你需要的sleep1000#删除锁文件rm-f${LOCKFILE}我们尝试运行其中一个,然后再尝试运行另一个窗口:$./test.sh./test.shalreadyrunning由于已经有实例在运行,发现新程序运行不了。并且在旧脚本运行完之后,新脚本就可以运行了。其实这里有几点很巧妙:kill-0`cat\${LOCKFILE}`这个用来检测进程是否存在,防止进程消失,但是锁文件还在,导致后续脚本无法运行。trap"rm-f\${LOCKFILE};exit"INTTERMEXIT用于确保在脚本退出时删除锁文件。rm-f{LOCKFILE}脚本最后需要删除锁文件flock说到锁文件,这里就不得不提到flock命令了。如果没有上面的一些巧妙处理,我们往往很难删除原来创建的锁文件。比如脚本意外中断,来不及执行删除多个脚本,导致竞争,导致判断异常。锁定文件已准备好在下一步创建,但首先创建了另一个脚本,这将导致异常。所以我们可以考虑使用flock:#!/usr/bin/envbashLOCK_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""$@"||:#Otherthingsthescript会做sleep1024解释的事情:如果没有设置${FLOCKER}环境变量,尝试锁定脚本本身,如果锁定成功,则运行当前脚本(使用原始参数),否则,静默退出。总结一下单例运行的思想本身很简单,就是检测当前是否有实例在运行,如果有就退出,但是这里怎么判断就没那么容易了。最后总结一下本文应该掌握的一些信息:$0脚本名称$@脚本参数$$当前脚本进程id$?last命令执行结果描述符0标准输入描述符1标准输出描述符2标准错误>重新定向