你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

Shell脚本单例运行

[复制链接]
gaosmile 发布时间:2021-2-7 10:20
假设你的一个脚本已经在运行了,如果避免再次被执行呢?也就是如何实现单例运行?
看起来可行的方法
一个非常简单的思路就是,新的脚本被执行时,先检测当前脚本是否有其他实例正在运行,如果有则直接退出。
#!/usr/bin/env bash
' O3 V) A3 c5 C+ l% A#test.sh 来源:公众号编程珠玑
4 D& h/ K' `( P/ R9 j7 h#获取当前运行的test.sh脚本数/ D* f0 r% ~5 d+ y
runCount=$(ps -ef|grep test.sh | grep -v grep -c); W  `6 l* W# u5 L% V& E& Z. G
if [ "${runCount}" -ge 1 ], c" Z9 Z5 T& N! R; `7 B. f! n1 ]! Q
then, t7 ^1 j) J0 c  S: I! U
    echo -e "test.sh already running,num{runCount}"4 l! P7 x7 |; f7 F* q" g* q
    exit 1;
$ S4 L* E: h) X5 Hfi2 i& b% i2 @3 g3 H7 F
while true
/ I* X' ^. s6 H5 Jdo4 q9 ~! {( n8 m# h0 ]1 h( q
    echo "test.sh run"
- H& V" `1 y% k    sleep 18 l# v( E7 r9 G' p6 L; z# h; n& `- @
done
7 U& d* e5 ^5 Y( A: ~
这里通过ps获取到当前在运行的test.sh脚本数,如果大于1,说明已经有在运行的了。
但是你运行会发现,其程序数量不只是一个。
$ ./test.sh
4 a2 H6 |* S: }& Utest.sh already running,num:2( O# h; g1 k! ^5 H* ]
惊不惊喜?为什么为这样呢?原因在于,shell脚本中一个命令执行相当于fork了一个进程执行,这里执行的是查找tesh.sh并grep的程序,另外还有一个就是当前运行的脚本程序,这样的方式自然就会出现每次都有两个了。
: f/ j/ [, I8 s( W! d# C  S  ^当然判断条件这里你可以换一下,例如数量大于2,但终归不太好。
文件锁
实际上这种方法你已经在《如何让你的程序同时只有一个在运行》介绍过了,只不过之前是用于编写C/C++程序,而这里是用于shell脚本。
我们来回顾一下,这是一个怎样的过程:
  • 1.运行前检查是否有该锁文件,并且文件中的进程正在运行
  • 2.如果有并且程序正在运行,则已经有实例在运行
  • 3.否则,无实例,创建锁文件,写入进程id
  • 4.退出时,删除锁文件

    : ?2 h/ J! T7 Z1 W- D, Y, j- k
解释一下第一条,为什么一定要确定锁文件中的进程正在运行,因为,有些情况下如果运行的时候退出没有删除该文件,则会导致新的实例永远无法运行。
#!/usr/bin/env bash6 r3 _8 E* Z6 l  w
#来源:公众号编程珠玑
9 O! X( G! e$ ?LOCKFILE=/tmp/test.lock
5 F2 L7 w- O1 f! C& dif [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then0 u; H) L  r* X8 O' E7 ~- [5 v
    echo " $0 already running", I0 Z2 {. I3 a
    exit: K+ @  d) m* n1 N4 d# P- l
fi
$ H3 {) i0 y9 k% j4 P) u& H# T* B: \" W1 }
# 确保退出时,锁文件被删除$ K, b$ R! }! y) [* {
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
8 t3 T0 \( c: r. s& i#将当前程序进程id写入锁文件
+ ]' |* b$ a/ ^0 e9 }0 |# Decho $$ > ${LOCKFILE}
3 z6 y6 ^: W8 d. F! j: T0 i( P' X2 X/ Y% Z- T3 Y
# 做你需要的事情8 ]: [: N* D7 [3 V* e) z+ A
sleep 1000
( o# \* F. p# F0 m7 T- }* ~. k% I
/ m# ^2 z2 u$ P, J: @2 D6 z: u/ q# 删除锁文件
8 {/ w: f% ~' l' ~3 lrm -f ${LOCKFILE}
. x- `) A' W+ l# g# K' t2 V0 i0 e& \
我们试着运行其中一个,然后另外一个窗口尝试运行:
$ ./test.sh; ?& ~5 G! {( D
./test.sh already running
. J* d" o. }4 r5 h7 y
由于已经有实例在运行,发现新的程序无法运行了。而等旧的脚本运行完之后,新的就可以运行了。
实际上这里面有几个点非常巧妙:
  • kill -0 `cat \${LOCKFILE}`  这里用于检测该进程是否存在,避免进程不在了,但是锁文件还在,导致后面的脚本无法运行。
  • trap "rm -f \${LOCKFILE}; exit" INT TERM EXIT 用于确保脚本退出时,锁文件会被删除。
  • rm -f {LOCKFILE}  脚本最后需要删除锁文件

    # i0 {$ h6 q: \* m5 r( b) _3 z
flock
说到锁文件,这里就不得不提flock命令了。没有前面的一些巧妙处理,我们很多时候会很难删除原先创建的锁文件,比如:
  • 脚本被意外中断,没来得及执行删除
  • 多个脚本产生竞争,导致判断异常,比如前面有一个脚本运行,判断没有锁文件,下一步准备创建,但是另外一个脚本又先创建了,就会导致异常了。

    $ _, P3 s, s9 j1 ]: T, {0 B: k0 ^5 x
因此我们可以考虑使用flock:
#!/usr/bin/env bash7 M$ D; s5 i- z, O8 G/ L0 i
#来源:公众号编程珠玑- Z% T) q+ V, K* m1 ~+ V
LOCK_FILE=/tmp/test.lock7 \- Y+ K! e2 L( ^9 P
exec 99>"$LOCK_FILE"; N: p  Q9 B7 y0 w5 n5 M# ^
flock -n 99: J, _3 o; ^; O3 s) {8 F- A
if [ "$?" != 0 ]; then
7 q) s8 K( w" w8 k$ X& O    echo "$0 already running"
# O3 i4 u$ a  H    exit 1
0 |: h7 V/ R, o% c: `) K0 Efi% J  E. |1 g" I
#脚本要做的其他事情  k& \3 L, t& w& D$ E; o
sleep 1024
! J* h4 b% y; V; m3 m4 H2 e: D7 X  W
解释一下:
  • exec 99>"$LOCK_FILE" 表示创建文件描述符99,指向锁文件,为何是99?110其实也是可以的,只是为了和当前脚本可能打开的文件描述符冲突(例如和0,1,2冲突)。
  • flock -n 99  尝试对该文件描述符加锁,由操作系统保证原子性
  • 一旦flock失败了,我们这里可以退出
  • 而即使锁定了,脚本退出后,也会自动释放
    7 k3 G% p, v) g
因此这里避免了锁没有释放的情况。
另一种做法
查看flock的man手册,我们发现它还有一个例子是这么做的:
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en  "$0"  "$0"  "$@" || :% T2 d9 F- Y" v, w7 M( n9 b9 P* i
在脚本开头加上上面这么一行就可以了。例如:
#!/usr/bin/env bash
# |4 }0 a* v; Y#来源:公众号编程珠玑; j- p2 n" x4 K4 V
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en  "$0"  "$0"  "$@" || :* h8 m: ]1 o) A+ g
#脚本要做的其他事情8 Q, n6 Z) w# J0 \( |/ P
sleep 1024
8 r) h' @' C! ?" I1 f2 {" i7 S
解释一下:
  • 如果${FLOCKER}环境变量没有设置,则尝试将脚本本身加锁,如果加锁成功,则运行当前脚本,(并且带上原有的参数),否则的话静默退出。
    5 z* H3 U+ e" ]0 C# ~! i8 _
总结
单例运行本身思路是很简单的,就是探测当前是否有实例在运行,如果有,则退出,但是这里如何判断,却并不是那么容易。
最后,总结一下本文出现的一些该掌握的信息
  • $0  脚本名称
  • $@  脚本参数
  • $$ 当前脚本进程id
  • $? 上一条命令执行结果
  • 描述符0 标准输入
  • 描述符1 标准输出
  • 描述符2 标准错误
  • > 重定向

    0 f! r# y5 L* S3 ?6 |
- a: h/ r" Y6 V+ ~) j  A/ x4 B
赞 收藏 评论0 发布时间:2021-2-7 10:20

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版