设为首页 收藏本站
查看: 1377|回复: 0

(转)shell 脚本里锁文件的办法

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2015-12-15 14:12:50 | 显示全部楼层 |阅读模式
shell 脚本里锁文件的办法


转自:http://zhu8337797.blog.163.com/blog/static/170617549201122891925769/


1. util-linux flock


这个命令有两种用法:  

(1)       flock LOCKFILE COMMAND  

(2)       ( flock -s 200; COMMAND; ) 200>LOCKFILE

flock 需要保持打开锁文件,对于第二种使用方式并不方便,而且 -s 方式指定文件句柄可能冲突。好处是不需要显式的解锁,进程退出后锁必然释放。


2. liblockfile1 dotlockfile


号称最灵活可靠的文件锁实现。其等待时间跟  -r 指定的重试次数有关,重试时间为 sum(5, 10, ..., min(5*n, 60), ...).锁文件不需要保持打开, 带来的问题是需要用 trap EXIT确保进程退出时删除锁文件.


3. procmail lockfile


dotlockfile 类似, 但可以一次性创建多个锁文件.



```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````



SHELL中实现文件锁,有两种简单的方式。


1)一是利用普通文件,在脚本启动时检查特定文件是否存在,如果存在,则等待一段时间后继续检查,直到文件不存时创建该文件,在脚本结束时删除文件。为确保脚本在异常退出时文件仍然能被删除,可以借助于trap "cmd" EXIT TERM INT命令。一般这类文件存放在/var/lock/目录下,操作系统在启动时会对该目录做清理。


2)另一种方法是是使用flock命令。使用方式如下,这个命令的好处是等待动作在flock命令中完成,无需另外添加代码。


(  flock 300  ...cmd...  flock -u 300  ) > /tmp/file.lockflock有个缺陷是,在打开flock之后fork(),子进程也会拥有锁,如果在flock其间有运行daemon的话,必需确保daemon在启动时已经关闭了所有的文件句柄,不然该文件会因为daemon一直将其置于打开状态而无法解锁。


```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````



一个实现linux shell文件锁的例子


最近看到很多讨论如何能不让脚本重复执行的问题,实际就是文件锁的概念,写了一个小例子:

把这个作为文件开头不会产生重复执行的情况。(我想两个执行脚本的文件名一模一样应该不会经常出现吧)


#!/bin/bash
LockFile()
{
  find /dev/shm/* -maxdepth 0 -type l -follow -exec unlink {} \;
  [ -f /dev/shm/${0##*/} ] && exit
  ln -s /proc/$$ /dev/shm/${0##*/}
  trap "Exit" 0 1 2 3 15 22 24
}
Exit()
{
  unlink /dev/shm/${0##*/};
  exit 0;
}
LockFile
# main program
# program ... ...
# Exit


/var/lock/subsys目录的作用的说明


很多程序需要判断是否当前已经有一个实例在运行,这个目录就是让程序判断是否有实例运行的标志,比如说xinetd,如果存在这个文件,表示已经有xinetd在运行了,否则就是没有,当然程序里面还要有相应的判断措施来真正确定是否有实例在运行。


通常与该目录配套的还有/var/run目录,用来存放对应实例的PID,如果你写脚本的话,会发现这2个目录结合起来可以很方便的判断出许多服务是否在运行,运行的相关信息等等。



   实际上,判断是否上锁就是判断这个文件,所以文件存在与否也就隐含了是否上锁。而这个目录的内容并不能表示一定上锁了,因为很多服务在启动脚本里用touch来创建这个加锁文件,在系统结束时该脚本负责清除锁,这本身就不可靠(比如意外失败导致锁文件仍然存在),我在脚本里一般是结合PID文件(如果有PID文件的话),从PID文件里得到该实例的PID,然后用ps测试是否存在该PID,从而判断是否真正有这个实例在运行,更加稳妥的方法是用进程通讯了,不过这样的话单单靠脚本就做不到了。



Flock用法

flock命令在我的系统属于util-linux-2.13-0.46.fc6包,如果没有此命令,尝试更新您系统下的util-linux包。

介绍此命令的原因:

论坛中曾有woodie兄写的脚本串行化的讨论,已经很完善了。

flock此命令既与shell脚本结合的很好,而且与C/PERL/PHP等语言的flock函数用法很相似,使用起来也很简单。相比之下,woodie兄那篇的内容需要不浅的shell功底来理解。

两种格式分别为:

       flock [-sxon] [-w timeout] lockfile [-c] command...


       flock [-sxun] [-w timeout] fd

介绍一下参数:

-s为共享锁,在定向为某文件的FD上设置共享锁而未释放锁的时间内,其他进程试图在定向为此文件的FD上设置独占锁的请求失败,而其他进程试图在定向为此文件的FD上设置共享锁的请求会成功。

-e为独占或排他锁,在定向为某文件的FD上设置独占锁而未释放锁的时间内,其他进程试图在定向为此文件的FD上设置共享锁或独占锁都会失败。只要未设置-s参数,此参数默认被设置。

-u手动解锁,一般情况不必须,当FD关闭时,系统会自动解锁,此参数用于脚本命令一部分需要异步执行,一部分可以同步执行的情况。

-n为非阻塞模式,当试图设置锁失败,采用非阻塞模式,直接返回1,并继续执行下面语句。

-w设置阻塞超时,当超过设置的秒数,就跳出阻塞,返回1,并继续执行下面语句。

-o必须是使用第一种格式时才可用,表示当执行command前关闭设置锁的FD,以使command的子进程不保持锁。

-c执行其后的comand

举个实用的例子:


#! /bin/bash -


set -x

#exec 6"mylockfile.sh"

{

         flock -n 6

#       [ "$?" -eq "1" ] && {echo "fail"; exit;}

         if [ "$?" -eq "1" ]; then

                   echo "fail";

                   exit;

         fi

         echo $$

         sleep 10

} 6"mylockfile.sh"

#}

此例的功能为当有一个脚本实例正在执行时,另一个试图执行该脚本的进程会失败退出。

sleep那句可以换成您需要执行的语句段。

这里请注意一点,我使用打开mylockfile,原因是定向文件描述符是先于命令执行的。因此假如在您要执行的语句段中需要读写mylockfile文件,例如想获得上一个脚本实例的pid,并将此次的脚本实例的pid写入mylockfile。此时直接用>打开mylockfile会清空上次存入的内容,而用>token.log  
else  
  printf "cannot create pipe file /tmp/p-aquire\n" >>token.log  
  exit 1  
fi  
  
loop_times_before_check=100  
if [ -n "$1" ];then  
  limit=$1  
else  
  # default concurrence is 1  
  limit=1  
fi  
number_of_running=0  
counter=0  
while :;do  
  #check stale token, which owner is died unexpected  
  if [ "$counter" -eq "$loop_times_before_check" ]; then  
    counter=0  
    for pid in `cat token_file`;do  
      pgrep $pid  
      if [ $? -ne 0 ]; then  
        #remove lock  
            printf "s/ $pid//\nwq\n"|ed -s token_file  
            number_of_running=`expr $number_of_running - 1`  
      fi  
    done  
  fi  
  counter=`expr $counter + 1`  
  
  #  
  if [ "$number_of_running" -ge "$limit" ];then  
    # token is all given out. bypass all request until a instance to give one back  
    pid=`sed -n '/stop/ {s/\([0-9]\+\) \+stop/\1/p;q}' /tmp/p-aquire`  
    if [ -n "$pid" ]; then  
      # get a token returned  
      printf "s/ $pid//\nwq\n"|ed -s token_file  
      number_of_running=`expr $number_of_running - 1`  
      continue  
    fi  
  else  
    # there is still some token to give out. serve another request  
    read pid action < /tmp/p-aquire  
        if [ "$action" = stop ]; then  
          #  one token is given back.  
          printf "s/ $pid//\nwq\n"|ed -s token_file  
          number_of_running=`expr $number_of_running - 1`  
        else  
          # it's a request, give off a token to instance identified by $pid  
          printf " $pid" >> token_file  
          number_of_running=`expr $number_of_running + 1`  
        fi  
  fi  
done  

--------------------------------------------------------------------------------------------

修订记录:

1.修正token.sh的一个BUG,将原来用sed删除失效令牌的命令用ed命令代替。感谢r2007waker两位指出错误!

--------------------------------------------------------------------------------------------


脚本2:并发执行的脚本 -- my-script。在"your code goes here"一行后插入你自己的代码,现有的是我用来测试的。



#!/bin/sh  
# second to wait that the ditributer gives off a token  
a_while=1  
if [ ! -p /tmp/p-aquire ]; then  
  printf "cannot find file /tmp/p-aquire\n" >&2  
  exit 1  
fi  
# try to aquire a token  
printf "$$\n" >> /tmp/p-aquire  
sleep $a_while  
# see if we get one  
grep "$$" token_file  
if [ $? -ne 0 ]; then  
  # bad luck. :(  
  printf "no token free now, exitting...\n" >&2  
  exit 2  
fi  
# yeah, got token!  
# be sure to return the token before we exit  
trap 'printf "$$ stop\n" > /tmp/p-aquire' 0  
trap "exit 3" 1 2 3 15  
  
#get to run, your code goes here  
printf "$$: running...\n" >&2  
sleep 5  
printf "$$: exitting...\n" >&2  
#end of your code  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-151602-1-1.html 上篇帖子: shell之文件锁 [转贴] 下篇帖子: Powershell 设置JDK环境和修改环境变量
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表