Linux shell脚本单进程执行

在CI中,为了实现shell脚本的单例执行,文章探讨了两种方案。第一种通过检查pid文件数量,但存在等待堆积的问题;第二种方案利用flock进行文件锁定,确保脚本在已运行状态下不会重复执行,且在程序结束时自动释放锁。

简要

在持续集成(CI)中, 我们的项目使用的是 shell, 某个stages是需要单例执行(因为要独占进程). 因此想到了要使用单例. 等待执行.

第一种方案

代码

#!/bin/bash
file_name=`basename $0`
echo $file_name
while [ `pgrep -f ${file_name} | wc -l` -gt 2 ]; do
        echo ${file_name} process existed, sleep 5s
        sleep 5
done

echo 开始执行
sleep 8
echo 执行结束

解释

bashename $0 只取文件名
例如 bash ~/demo/test.sh $0=~/demo/test.sh bashename $0=test.sh

pgrep -f ${file_name}输出含有file_name的pid
wc -l 计数
理论上 大于1即可, 但是为什么要大于2呢, 本身占一个进程(bash ./test.sh), 内部又有执行外部命令pgrep -f ${file_name} | wc -l 占一个进程, 共两个进程, 如果在执行一次bash ./test.sh必定大于2了.

该方法理论上看上去没什么问题, 但是当堆积起来时(比如堆积3个时), 必定都陷入了等待. 两个是没有问题的看图.
稍微改了下代码, 便于查看 count数

#!/bin/bash
file_name=`basename $0`
echo $file_name
count=`pgrep -f ${file_name} | wc -l`
while [ $count -gt 2 ]; do
        echo ${file_name} process existed, sleep 5s count=$count
        sleep 5
        count=`pgrep -f ${file_name} | wc -l`
done

echo 开始执行
sleep 8
echo 执行结束

结果显示

只有两个的情况下
在这里插入图片描述
三个情况, 就会死循环了…
在这里插入图片描述
因此这种方法被舍弃, 主要还有一点就是pgrep -f ${file_name}输出含有file_name的pid, 注意是含有, 当有执行例如vim test.sh时候, 也会记录count

第二种方案

使用flock, 先看看 flock如何使用

$ flock -h

Usage:
 flock [options] <file>|<directory> <command> [<argument>...]
 flock [options] <file>|<directory> -c <command>
 flock [options] <file descriptor number>

Manage file locks from shell scripts.

选项:
 -s, --shared             get a shared lock
 -x, --exclusive          get an exclusive lock (default)
 -u, --unlock             remove a lock
 -n, --nonblock           fail rather than wait
 -w, --timeout <secs>     wait for a limited amount of time
 -E, --conflict-exit-code <number>  exit code after conflict or timeout
 -o, --close              close file descriptor before running command
 -c, --command <command>  run a single command string through the shell
     --verbose            increase verbosity

 -h, --help     display this help and exit
 -V, --version  output version information and exit

For more details see flock(1).

-s为共享锁,在定向为某文件的FD上设置共享锁而未释放锁的时间内,其他进程试图在定向为此文件的FD上设置独占锁的请求失败,而其他进程试图在定向为此文件的FD上设置共享锁的请求会成功。
-e为独占或排他锁,在定向为某文件的FD上设置独占锁而未释放锁的时间内,其他进程试图在定向为此文件的FD上设置共享锁或独占锁都会失败。只要未设置-s参数,此参数默认被设置。
-u手动解锁,一般情况不必须,当FD关闭时,系统会自动解锁,此参数用于脚本命令一部分需要异步执行,一部分可以同步执行的情况。
-n为非阻塞模式,当试图设置锁失败,采用非阻塞模式,直接返回1,并继续执行下面语句。
-w设置阻塞超时,当超过设置的秒数,就跳出阻塞,返回值设置为1,并继续执行下面语句。
-o必须是使用第一种格式时才可用,表示当执行command前关闭设置锁的FD,以使command的子进程不保持锁。
-c执行其后的comand。

最终使用 flock -n

代码

#!/bin/bash
file_name=`basename $0`
echo $file_name

readonly LOCKFILE="~/$file_name.pid"
readonly FD=$(ls -l /proc/$$/fd | sed -n '$p' | awk '{print $9}')
readonly LOCKFD=$(( ${FD}+1 ))

eval "exec ${LOCKFD}>${LOCKFILE}"
flag=0
while [ $flag -eq 0 ]; do
        flag=1
        flock -n ${LOCKFD} && echo $$>&${LOCKFD} || flag=0
        echo $flag
        sleep 1
done

echo "开始执行"
sleep 5
echo "z执行结束"

解释

readonly表示变量只读
LOCKFILE 正如 在使用apt-get的时候发生错误会说 xxx.pid被占用, 跟那个同样的道理
FD文件描述符
eval "exec ${LOCKFD}>${LOCKFILE}" 打开文件LOCKFILE并把它关联到文件描述符LOCKFD
while之后就一个循环, 获得到锁, 就不会执行 || flag=0 就会退出循环
flock -n ${LOCKFD}尝试获得锁, 得到锁, 会把自己的pid$$写入到文件中.

结果

在这里插入图片描述

最后

可能有人会说, 怎么不需要释放锁, 因为, 程序结束, 会自动释放释放文件描述符, 也就自动释放了锁, 如果程序中还有别的操作, 建议手动释放.

可能还有人会说, 我想让它执行一次就行了, 不需要每个脚本都执行.那么把代码改成这样就行了, ||flag=0改成’exit 1`即可

#!/bin/bash
file_name=`basename $0`
echo $file_name

readonly LOCKFILE="~/$file_name.pid"
readonly FD=$(ls -l /proc/$$/fd | sed -n '$p' | awk '{print $9}')
readonly LOCKFD=$(( ${FD}+1 ))

eval "exec ${LOCKFD}>${LOCKFILE}"
flock -n ${LOCKFD} && echo $$>&${LOCKFD} || exit 1

echo "开始执行"
sleep 5
echo "z执行结束"

其他shell就直接退出了, 不在执行
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值