确保只运行一个Bash脚本实例的最佳方法是什么?

100 linux bash lockfile pid flock

确保只有给定脚本的一个实例运行的最简单/最好的方法是什么 - 假设它是Linux上的Bash?

目前我正在做:

ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh
Run Code Online (Sandbox Code Playgroud)

但它有几个问题:

  1. 它把检查放在脚本之外
  2. 它不允许我从单独的帐户运行相同的脚本 - 我有时会这样.
  3. -C 仅检查进程名称的前14个字符

当然,我可以编写自己的pidfile处理,但我觉得应该有一个简单的方法来实现它.

prz*_*moc 139

咨询锁定已经使用了很长时间,它可以在bash脚本中使用.我更喜欢简单flock(从util-linux[-ng])到lockfile(从procmail).并始终记住这些脚本中退出时的陷阱(sigspec == EXIT0捕获特定信号是多余的).

在2009年,我发布了我的可锁定脚本样板(最初在我的维基页面上提供,现在可以作为要点).将其转换为每用户一个实例是微不足道的.使用它,您还可以轻松地为需要某些锁定或同步的其他场景编写脚本.

为方便起见,这是上面提到的样板.

#!/bin/bash
# SPDX-License-Identifier: MIT

## Copyright (C) 2009 Przemyslaw Pawelczyk <przemoc@gmail.com>
##
## This script is licensed under the terms of the MIT license.
## https://opensource.org/licenses/MIT
#
# Lockable script boilerplate

### HEADER ###

LOCKFILE="/var/lock/`basename $0`"
LOCKFD=99

# PRIVATE
_lock()             { flock -$1 $LOCKFD; }
_no_more_locking()  { _lock u; _lock xn && rm -f $LOCKFILE; }
_prepare_locking()  { eval "exec $LOCKFD>\"$LOCKFILE\""; trap _no_more_locking EXIT; }

# ON START
_prepare_locking

# PUBLIC
exlock_now()        { _lock xn; }  # obtain an exclusive lock immediately or fail
exlock()            { _lock x; }   # obtain an exclusive lock
shlock()            { _lock s; }   # obtain a shared lock
unlock()            { _lock u; }   # drop a lock

### BEGIN OF SCRIPT ###

# Simplest example is avoiding running multiple instances of script.
exlock_now || exit 1

# Remember! Lock file is removed when one of the scripts exits and it is
#           the only script holding the lock or lock is not acquired at all.
Run Code Online (Sandbox Code Playgroud)

  • @overthink只有`>`旁边的文字编号被认为是文件描述符编号,因此没有`eval`,``exec`会尝试执行名为`99`的二进制文件(或其他任何放在`$ LOCKFD`中的文件).值得补充的是,有些shell(比如`dash`)有一个需要fd编号为单位数的bug.我选择了高fd编号以避免可能的冲突(但它们取决于用例).由于陷阱IIRC中的"EXIT"条件很方便,我也选择了BASH,但看起来我错了,因为[它是POSIX shell的一部分](http://pubs.opengroup.org/onlinepubs/009695399/utilities/trap的.html#tag_04_146). (4认同)
  • @CarlosP:不.引擎盖下`flock`只使用[flock(2)](http://man7.org/linux/man-pages/man2/flock.2.html)系统调用,它没有提供这样的功能信息也不应该.如果你想不可靠地检查,是否存在锁(或没有锁),即没有持有它,那么你必须尝试以非阻塞方式获取它(`exlock_now`)并立即释放它(`unlock `)如果你成功了.如果您认为需要检查锁定状态而不更改其状态,那么您可能使用错误的工具来解决问题. (3认同)
  • @JayParoline 你误解了你所观察到的。当你杀死 (`-9`) 脚本,即运行脚本文件的 bash 实例时,它肯定会死,但是从它处理 `fork()`+`exec()`-ed(就像你的 sleep 一样)继承了 open 的副本文件描述符和 `flock()` 锁。在 sleep 睡眠时杀死脚本不会解锁,因为 sleep 进程仍然持有锁。对于可锁定脚本,这很重要,因为您通常希望保护“环境”(不要在 _something_ 仍在运行时启动另一个实例)。 (3认同)
  • 我觉得我错过了一些明显的东西,但为什么 exec 调用必须包含在 eval 中?例如,为什么`eval "exec $LOCKFD&gt;\"$LOCKFILE\""` 而不是`exec $LOCKFD&gt;"$LOCKFILE"`? (2认同)
  • 这个模板非常酷.但是我不明白你为什么要这样做._lock xn && rm -f $ LOCKFILE; }.刚刚解锁后,xn锁的目的是什么? (2认同)
  • @GeorgeYoung只有在立即独占锁定成功(早先解锁之后)的情况下才删除脚本末尾的锁定文件,这是因为其他脚本实例耐心地等待获得独占或共享锁定(即使用`exlock`或`shlock`),因为当它最终启动时,之前的实例不应该删除该文件. (2认同)
  • @JayParoline但是您可以通过在您的东西之前添加`(eval“ exec $ LOCKFD&gt;&-”`并在其后添加`)来更改上述行为,因此在该块中运行的所有内容都不会继承LOCKFD(显然是锁放了在上面)。 (2认同)
  • @przemoc我们一直在使用这段代码,这里有一个微妙的竞争条件.当第一个进程退出时,第二个进程可以在"_lock xn"和"&& rm -f $ LOCKFILE"之间的锁定文件上获取文件描述符.然后第二个进程正在运行,但锁文件将通过"rm"从文件系统中取消链接.第三个进程可以出现,创建一个同名的新锁文件,获取一个锁,然后开始运行.获得锁定后,您需要检查您获得的内容是否仍在文件系统上 - http://stackoverflow.com/questions/17708885/ (2认同)

ezp*_*zpz 102

如果所有用户的脚本相同,则可以使用lockfile方法.如果获得锁定,请继续显示消息并退出.

举个例子:

[Terminal #1] $ lockfile -r 0 /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] lockfile: Sorry, giving up on "/tmp/the.lock"

[Terminal #1] $ rm -f /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] $ 
Run Code Online (Sandbox Code Playgroud)

/tmp/the.lock已经获得你的脚本将是唯一一个能够访问执行.完成后,只需取下锁即可.在脚本形式中,这可能如下所示:

#!/bin/bash

lockfile -r 0 /tmp/the.lock || exit 1

# Do stuff here

rm -f /tmp/the.lock
Run Code Online (Sandbox Code Playgroud)

  • 我的linux上没有lockfile程序,但有一件事困扰我 - 如果第一个脚本在不删除锁定的情况下死掉它会有效吗?即在这种情况下,我希望下一次运行脚本运行,而不是死"因为以前的副本仍在工作" (10认同)
  • @ user80168当前的Ubuntu(14.04)提供了一个名为"lockfile-progs"(NFS安全锁定库)的软件包,它提供了lockfile- {check,create,remove,touch}.手册页说:"一旦文件被锁定,必须至少每五分钟触摸一次锁定,否则锁定将被视为陈旧,随后的锁定尝试将成功......".看起来像一个很好的包使用,并提到"--use-pid"选项. (7认同)
  • 我们可以有一个示例代码段吗? (3认同)
  • 添加了示例和框架脚本. (3认同)
  • 您还应该使用内置陷阱来捕获可能过早杀死脚本的任何信号.在脚本顶部附近,添加如下内容:trap"[ - f /var/run/my.lock] &&/bin/rm -f /var/run/my.lock"0 1 2 3 13 15您可以搜索/ usr/bin/*了解更多示例. (3认同)

Jak*_*ger 34

我认为flock这可能是最简单(也是最难忘)的变种.我在cron作业中使用它来自动编码dvdscds

# try to run a command, but fail immediately if it's already running
flock -n /var/lock/myjob.lock   my_bash_command
Run Code Online (Sandbox Code Playgroud)

使用-w了超时或离开了选择等待,直到锁被释放.最后,手册页显示了多个命令的一个很好的示例:

   (
     flock -n 9 || exit 1
     # ... commands executed under lock ...
   ) 9>/var/lock/mylockfile
Run Code Online (Sandbox Code Playgroud)


Jam*_*979 7

使用该set -o noclobber选项并尝试覆盖公共文件.

一个简短的例子

if ! (set -o noclobber ; echo > /tmp/global.lock) ; then
    exit 1  # the global.lock already exists
fi

# ... remainder of script ...
Run Code Online (Sandbox Code Playgroud)

一个较长的例子

此示例将等待global.lock文件,但在过长时间后超时.

 function lockfile_waithold()
 {
    declare -ir time_beg=$(date '+%s')
    declare -ir time_max=7140  # 7140 s = 1 hour 59 min.

    # poll for lock file up to ${time_max}s
    # put debugging info in lock file in case of issues ...
    while ! \
       (set -o noclobber ; \
        echo -e "DATE:$(date)\nUSER:$(whoami)\nPID:$$" > /tmp/global.lock \ 
       ) 2>/dev/null
    do
        if [ $(($(date '+%s') - ${time_beg})) -gt ${time_max} ] ; then
            echo "Error: waited too long for lock file /tmp/global.lock" 1>&2
            return 1
        fi
        sleep 1
    done

    return 0
 }

 function lockfile_release()
 {
    rm -f /tmp/global.lock
 }

 if ! lockfile_waithold ; then
      exit 1
 fi
 trap lockfile_release EXIT

 # ... remainder of script ...
Run Code Online (Sandbox Code Playgroud)


(这与@Barry Kelly的这篇文章类似,后来被注意到了.)

  • 这样的缺点之一(与“ flock”风格的锁定相反)是您的锁不会在“ kill -9”,重启,断电等情况下自动释放。 (2认同)
  • 我不确定我是否理解为什么这是一个问题;你当然可以在你的程序启动后用 `flock` 获取一个带有动态文件名的锁,然后在不退出的情况下释放它。使用一些现代(bash 4.1)工具来避免需要手动分配 FD:`exec {lock_fd}&gt;"$filename" &amp;&amp; flock -x "$lock_fd" || { echo "锁定失败" &gt;&amp;2; 出口1;}; ...这里的东西...; 执行 {lock_fd}&gt;&amp;-` (2认同)