本文转载自微信公众号《Linux开发那些事》,作者LinuxThings。转载此文请联系Linux开发那些事公众号.在开发过程中,经常使用shell脚本来完成定时备份任务。通常的做法是通过系统计划任务定时执行备份脚本。想象一下这样的场景。当备份时间到了,备份脚本会自动执行。如果备份比较耗时,会一直持续到下次备份的时间到了,下次备份会自动调用备份脚本,相当于两个进程同时执行备份脚本,这可能会导致备份数据混乱或其他无法预见的问题更进一步。如果备份脚本的执行时间远大于设置的备份间隔,系统中会出现多个Bash实例同时执行该脚本,占用大量系统资源,影响正常业务的运行程式。那个怎么样?针对以上shell脚本重复执行的问题,本文将要介绍的flock命令可以解决这个问题控制逻辑互斥例1已有脚本a.sh,内容如下#!/bin/bashecho"[`date+'%Y-%m-%d%H:%M:%S'`]beginpid:$$..."sleep10echo"[`date+'%Y-%m-%d%H:%M:%S'`]endpid:$$..."在终端(记为终端1)执行flock-xn./f.lock-c./a.sh命令,结果如下[tt@ecs-centos-7lock_test]$flock-xn./f.lock-c./a.sh[2020-12-1010:10:45]beginpid:5359...[2020-12-1010:10:55]endpid:5359...在执行上述命令的过程中,打开另一个终端(记为终端2),执行同样的命令,结果如下[tt@ecs-centos-7lock_test]$flock-xn./f.lock-c./a.sh[tt@ecs-centos-7lock_test]$上面的命令flock-xn./f.lock-c./a.sh中的-x选项是独占锁,有时也称为写锁。这是默认选项。-n选项是非阻塞的。如果无法获取到锁,则立即返回失败,而不是等待锁被释放。-c选项后面是要执行的命令在终端1执行flock-xn./f.lock-c./a.sh命令锁定f.lock文件,执行运行./a.sh命令,执行过程会持续10秒左右(sleep10语句)。由于终端2的flock-xn./f.lock-c./a.sh命令是在终端1命令执行的过程中执行的,此时终端1还没有释放f.lock文件锁,而-n选项是非阻塞的,所以终端2不会阻塞等待f.lock文件锁,而是立即返回到终端2。如果执行flock-x./f.lock-c./a.sh命令会阻塞等待,直到1号终端释放f.lock文件锁,然后获取f.lock文件锁,开始执行./a.shcommandinstance2instance1每次都需要执行flock-xnfilelock-c./a.sh命令,每个不能重复执行的脚本都要分配一个文件锁。还需要保证不同的脚本使用不同名称的文件锁。有没有办法执行./a.sh命令实现例1的功能呢?答:有的,我们稍微修改一下a.sh,修改后的内容如下1#!/bin/bash234echo"[`date+'%Y-%m-%d%H:%M:%S'`]1111pid:$$...MY_LOCK:${MY_LOCK}"56["${MY_LOCK}"!="$0"]&&execenvMY_LOCK="$0"flock-xn"$0""$0""$@"78echo"[`date+'%Y-%m-%d%H:%M:%S'`]beginpid:$$...MY_LOCK:${MY_LOCK}"910sleep101112echo"[`date+'%Y-%m-%d%H:%M:%S'`]endpid:$$..."1号终端执行./a.sh命令,输出如下[tt@ecs-centos-7lock_test]$./a.sh[2020-12-1014:11:35]1111pid:5944...MY_LOCK:[2020-12-1014:11:35]1111pid:5946...MY_LOCK:./a.sh[2020-12-1014:11:35]beginpid:5946...MY_LOCK:./a.sh[2020-12-1014:11:45]endpid:5946...终端1命令执行过程中,终端2执行./a.sh命令,并且输出如下[tt@ecs-centos-7lock_test]$./a.sh[2020-12-1014:11:44]1111pid:5976...MY_LOCK:[2020-12-1014:11:44]与新的a.sh脚本相比原来增加了4和6两行。第4行是日志打印。第6行显示$0是脚本名,这里的值为./a.sh$@是传入a.sh脚本的所有参数。exec会在当前进程执行紧跟其后的命令,当前脚本进程没有执行过的命令将不会执行["${MY_LOCK}"!="$0"]是判断是否MY_LOCK环境变量与脚本名称(a.sh)一致,如果相同则执行envMY_LOCK="$0"命令和flock-xn"$0""$0""$@"命令envMY_LOCK="$0"设置环境变量MY_LOCK为脚本名flock-xn"$0""$0""$@"其实就是flock-xn./a.sh./a.sh,使用当前脚本名作为文件锁实例2,执行./a.sh命令后,运行到第6行时,MY_LOCK变量为空,所以["${MY_LOCK}"!="$0"]的结果为trueexec命令会忽略下面的内容未执行的命令,即当前shell进程中第6行之后的命令都不是那么,execenvMY_LOCK="$0"flock-xn"$0""$0""$@"命令就会被执行,其值MY_LOCK变量wi将设置为当前脚本名称./a.sh,同时执行flock-xn"$0""$0""$@"命令,这个命令会在一个新的子shell中执行./a.sh,所以脚本后面的输出中打印的进程ID和一开始是不一样的。同时,因为flock-xn"$0""$0""$@"之前执行过envMY_LOCK="$0",MY_LOCK变量的值设置为./a.sh,所以flock-xn"$0""$0""$@"命令重新执行。/a.使用sh命令时,脚本第6行["${MY_LOCK}"!="$0"]的结果为false,第6行exec后的命令将不会执行,并且脚本会从第7行一直执行到最后,结果第8行和第12行的输出日志也说明脚本已经执行完毕总结例1和例2提供了两种解决重复执行的方法脚本,主要是使用flock命令设置文件锁,例2的方法比较简单,只需要加上["${MY_LOCK}"!="$0"]&&execenvMY_LOCK="$0"flock-xn"$0""$0""$@"开头的语句脚本,调用脚本的命令不变flock命令的更多选项和用法可以通过manflock查看
