千家信息网

linux中shell如何实现文件锁

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,这篇文章给大家分享的是有关linux中shell如何实现文件锁的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。1. util-linux 的 flock这个命令有两种用法:
千家信息网最后更新 2026年11月02日linux中shell如何实现文件锁

这篇文章给大家分享的是有关linux中shell如何实现文件锁的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

1. util-linux 的 flock

这个命令有两种用法: flock LOCKFILE COMMAND ( flock -s 200; COMMAND; ) 200>LOCKFILEflock 需要保持打开锁文件,对于第二种使用方式并不方便,而且 -s 方式指定文件句柄可能冲突。好处是不需要显式的解锁,进程退出后锁必然释放。

2. liblockfile1 的 dotlockfile

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

3. procmail 的 lockfile

跟 dotlockfile 类似, 但可以一次性创建多个锁文件.
在SHELL中实现文件锁,有两种简单的方式。

一是利用普通文件,在脚本启动时检查特定文件是否存在,如果存在,则等待一段时间后继续检查,直到文件不存时创建该文件,在脚本结束时删除文件。为确保脚本在异常退出时文件仍然能被删除,可以借助于trap "cmd" EXIT TERM INT命令。一般这类文件存放在/var/lock/目录下,操作系统在启动时会对该目录做清理。
另一种方法是是使用flock命令。使用方式如下,这个命令的好处是等待动作在flock命令中完成,无需另外添加代码。
( flock 300 ...cmd... flock -u 300 ) > /tmp/file.lock
但flock有个缺陷是,在打开flock之后fork(),子进程也会拥有锁,如果在flock其间有运行daemon的话,必需确保daemon在启动时已经关闭了所有的文件句柄,不然该文件会因为daemon一直将其置于打开状态而无法解锁。

一个实现linux shell文件锁的例子
最近看到很多讨论如何能不让脚本重复执行的问题,实际就是文件锁的概念,写了一个小例子:
把这个作为文件开头不会产生重复执行的情况。(我想两个执行脚本的文件名一模一样应该不会经常出现吧)

#!/bin/bashLockFile(){ 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命令在我的系统属于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{flock -n 3[ $? -eq 1 ] && { echo fail; exit; }echo $$sleep 10} 3<>mylockfile

此例的功能为当有一个脚本实例正在执行时,另一个试图执行该脚本的进程会失败退出。
sleep那句可以换成您需要执行的语句段。
这里请注意一点,我使用<>打开mylockfile,原因是定向文件描述符是先于命令执行的。因此假如在您要执行的语句段中需要读写mylockfile文件,例如想获得上一个脚本实例的pid,并将此次的脚本实例的pid写入mylockfile。此时直接用>打开mylockfile会清空上次存入的内容,而用<打开mylockfile当它不存在时会导致一个错误。当然这些问题都可以用其他方法解决,我只是点出这种最通用的方法。

【背景介绍】

CU上曾经有几个帖子讨论到一个实际问题,就是如何限制同一时刻只允许一个脚本实例运行。其中本版新老斑竹和其它网友都参加了讨论,但以faintblue兄的帖子对大家启发最大,下面的背景介绍中许多内容都是来自于他。在此感谢faintblue兄,也感谢斑竹和其它朋友!
woodie总结了一下现有的结果,大体上可以分为两种思路:
一、简单的方法是,用ps一类命令找出已经运行脚本的数量,如果大于等于2(别忘了把自己也算进去^_^),就退出当前脚本,等于1,则运行。这种方法简单是简单,不过有一些问题:
首先,ps取得脚本文件进程数量就有很多陷阱,例如有时无法ps到脚本文件的名称;
即使可以ps到脚本名,如果用到管道的话,由于子shell的原因,在大多数平台下会得到奇怪的结果,有时得到数字a,有时又得到数字b,让人无所适从;
就算计数的问题已经解决了,还有问题,不过不太严重:如果两个脚本实例同时计数,显然数字都应该等于2,于是两个都退出了。于是在这一时间点上没有一个脚本在执行;

二、加锁的方法。就是脚本在执行开始先试图得到一个"锁",得到则继续执行,反之就退出。
加锁方法也存在一些问题,主要集中在两个方面:
其一,加锁时如何避免竞态条件(race condition)。即如何找到一些"原子"操作,使得加锁的动作一步完成,中间不能被打断。否则就可能出现下面的情况:
脚本1检测到没有锁被占用;
然后脚本2也检测到没有锁被占用;
脚本1加锁,开始执行;
然后脚本2(错误地)加锁,也开始执行;
看到吗,两个脚本在同时执行。:(
可能的一些加锁的"原子"操作有:
1.创建目录,当一个进程创建成功后其它进程都会失败;
2.符号链接:ln -s,一个链接创建后其它进程的ln -s命令会出错;
3.文件首行的竞争,多个进程以append的方式同时写到文件,只有惟一一个进程写到了文件的第一行,因为不可能有两个第一行。^_^
4.其它软件包的加锁工具,通常是c语言二进制程序,自己写的也行。
目前加锁时的问题已经可以解决。
其二,找到一种方法避免出现"死锁"的情况,这里是指:虽然"锁"被占用,但却没有脚本在执行。这通常在脚本意外退出,来不及释放占用的"锁"之后。如收到一些系统信号后退出,机器意外掉电后退出等。
对于前者的情况,可以用trap捕获一些信号,在退出前释放锁;但有些信号是无法捕获的。
对于后者,可以在机器重起后用脚本自动删除锁来解决。不过有点麻烦。
所以比较理想的是脚本自己来检测死锁,然后释放它。不过问题的难点在于如何找到一种"原子"操作,将检测死锁和删除死锁的动作一步完成,否则又会出现与加锁时同样的竞态条件的问题。例如:
进程1检测到死锁;
进程2监测到死锁;
进程1删除死锁;
进程x(也可能是进程1自己)加锁,开始运行;
进程2(错误地)删除死锁;
此时锁没有占用,于是任意进程都可以加锁并投入运行。
这样又出现了两个进程同时运行的情况。:(
可惜的是:在迄今为止的讨论之后,woodie还没有找到一种合适的"原子"操作。:(只是找到了一种稍微好些的办法:就是在删除时用文件的inode作标识,于是其它进程新建的锁(文件名虽然相同,但inode相同的机率比较微小)不容易被意外删除。这个方法已经接近完美了,可惜还是存在误删的微小几率,不能说是100%安全。唉,山重水复疑无路啊!:(

最近又有网友问起这个问题,促使我又再次思考。从我以前的一个想法发展了一下,换了一种思路,便有豁然开朗的感觉。不敢藏私,写出来请大家debug。^_^

基本的想法就是:借鉴多进程编程中临界区的概念,如果各个进程进入我们设立的临界区,只可能一个一个地顺序进入,不就能保证每次只有一个脚本运行了吗?怎样建立这样一种临界区呢?我想到了一种方法,就是用管道,多个进程写到同一个管道,只可能一行一行地进入,相应的,另一端也是一行一行地读出,如此就可以实现并行执行的多个进程进入临界区时的"串行化"。这与faintblue兄以前贴出的append文件的方法也是异曲同工。
我们可以让并行的进程同时向一个管道写一行请求,内容是其进程号,在管道另一端顺序读取这些请求,但只有第一个请求会得到一个"令牌",被允许开始运行;后续的请求将被忽略,对应的进程没有得到令牌,就自己退出。这样就保证了任意时间只有一个进程运行(严格地说是进入临界区)。说到"令牌",熟悉网络发展史的朋友可能会联想到IBM的Token Ring架构,每一时刻只能有一个主机得到令牌并发送数据,没有以太网的"碰撞"问题。可惜如同微通道技术一样,IBM的技术是不错,但最终还是被淘汰了。不错,这里令牌的概念就是借用于Token Ring。^_^
当一个进程执行完毕,向管道发送一个终止信号,即交回"令牌",另一端接受到后,又开始选取下一个进程发放"令牌"。
您可能会问了,那么死锁问题又如何解决呢?别急,我在以前的讨论中曾提出将检测处理死锁的代码单独拿出来,交给一个专门的进程来处理的想法,这里就具体实践这样一种思路。当检测和删除死锁的任务由一个专门的进程来执行时,就没有多个并发进程对同一个锁进行操作,所以竞态条件发生的物质基础也就根本不存在了。^_^
再发展一下这个思路,允许同时执行多个进程如何?当然可以!只要设立一个计数器,达到限制的数字就停止发放"令牌"即可。
下面就是woodie上述思路的一个实现,只是在centos 4.2下简单地测试了一下,可能还有不少错误,请大家帮忙"除虫"。^_^思路上有什么问题也请不吝指教:
脚本1,token.sh,负责令牌管理和死锁检测处理。与下一个脚本一样,为了保持脚本的最大的兼容性,尽量使用Bourne shell的语法,并用printf代替了echo,sed的用法也尽量保持通用性。这里是由一个命名管道接受请求,令牌在一个文件中发出。如果用ksh也许可以用协进程来实现,熟悉ksh的朋友可以试一试。^_^

#!/bin/sh #name: token.sh #function: serialized token distribution, at anytime, only a cerntern number of token given out #usage: token.sh [number] & #number is set to allow number of scripts to run at same time #if no number is given, default value is 1 if [ -p /tmp/p-aquire ]; then  rm -f /tmp/p-aquire fi if mkfifo /tmp/p-aquire; then  printf "pipe file /tmp/p-aquire created\n" >>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命令代替。感谢r2007和waker两位指出错误!
--------------------------------------------------------------------------------------------

脚本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

这个脚本是将文件锁得,不过我对这脚本还有一些疑惑的地方,暂且不讨论,等以后回头再来谈

#!/bin/sh# filelock - A flexible file locking mechanism.retries="10"      # default number of retriesaction="lock"      # default actionnullcmd="/bin/true"   # null command for lockfilewhile getopts "lur:" opt; do case $opt in  l ) action="lock"   ;;  u ) action="unlock"  ;;  r ) retries="$OPTARG" ;; esacdoneshift $(($OPTIND - 1))if [ $# -eq 0 ] ; then cat << EOF >&2Usage: $0 [-l|-u] [-r retries] lockfilenameWhere -l requests a lock (the default), -u requests an unlock, -r Xspecifies a maximum number of retries before it fails (default = $retries).EOF exit 1fi# Ascertain whether we have lockf or lockfile system appsif [ -z "$(which lockfile | grep -v '^no ')" ] ; then echo "$0 failed: 'lockfile' utility not found in PATH." >&2 exit 1fiif [ "$action" = "lock" ] ; then if ! lockfile -1 -r $retries "$1" 2> /dev/null; then  echo "$0: Failed: Couldn't create lockfile in time" >&2  exit 1 fielse  # action = unlock if [ ! -f "$1" ] ; then  echo "$0: Warning: lockfile $1 doesn't exist to unlock" >&2  exit 1 fi rm -f "$1"fiexit 0

感谢各位的阅读!关于"linux中shell如何实现文件锁"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

文件 脚本 进程 运行 命令 问题 死锁 令牌 实例 就是 方法 目录 一行 检测 两个 内容 情况 管道 同时 多个 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 湖北九亚互联网络科技有限公司 博彦科技互联网金融 广州淘业互联网络科技 互联网科技卖菜 九亚互联网科技 小马过河互联网科技 深圳齐家互联网科技 互联网高科技企业 科技互联网创业计划书 无锡溥仪互联网科技有限公司 安控科技rtu产品工业互联网 中盈科技互联网信息有限公司 互联网科技项目计划书材料类 崇明区互联网教育科技诚信经营 千方科技属于互联网企业吗 江阴互联网智能科技产品进口 每天都有哪些好的互联网科技 九思连强互联网电子科技有限公司 第三届互联网大会安防黑科技 互联网的黑科技实验室 湖南永感互联网科技有限公司 互联网科技战争 无线互联网科技 宁夏科技产业互联网案例 南平创想时代互联网科技有限公司 互联网媒体平台算高科技产业 玉溪新华互联网科技好不好 梅州GM互联网络科技有限公司 信承诺互联网科技有限公司 好易买互联网科技
0