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

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

目前我正在做:

ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh 

但它有几个问题:

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

当然,我可以编写自己的pidfile处理,但我觉得应该有一个简单的方法来做到这一点。

如果脚本在所有用户中都是相同的,则可以使用lockfile方法。 如果您获得locking,请继续显示消息并退出。

举个例子:

 [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] $ 

在获得/tmp/the.lock之后,你的脚本将是唯一可以执行的脚本。 当你完成后,只要取出锁。 在脚本forms这可能看起来像:

 #!/bin/bash lockfile -r 0 /tmp/the.lock || exit 1 # Do stuff here rm -f /tmp/the.lock 

咨询locking已经使用了很长时间,可以在bash脚本中使用。 我更喜欢简单的flock (来自util-linux[-ng] ),而不是lockfile (来自procmail )。 并且在这些脚本中总是记住关于退出的陷阱(sigspec == EXIT0 ,陷印特定的信号是多余的)。

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

这里是提到的样板为了您的方便。

 #!/bin/bash ## 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. 

我认为flock可能是最简单的(也是最值得纪念的)变种。 我在cron作业中使用它来自动编码dvd和cds

 # try to run a command, but fail immediately if it's already running flock -n /var/lock/myjob.lock my_bash_command 

超时使用-w选项,或者等到locking释放后再等待。 最后,手册页为多个命令显示了一个很好的例子:

  ( flock -n 9 || exit 1 # ... commands executed under lock ... ) 9>/var/lock/mylockfile 

我不确定是否有一个强大的解决scheme,所以你最终可能会推出自己的产品。

锁文件不完善,但比使用'ps |更less grep | grep -v'pipe道。

话虽如此,你可以考虑保持过程控制与脚本分离 – 有一个开始脚本。 或者,至less将它分解到单独的文件中保存的函数,所以你可能在调用者脚本中有:

 . my_script_control.ksh # Function exits if cannot start due to lockfile or prior running instance. my_start_me_up lockfile_name; trap "rm -f $lockfile_name; exit" 0 2 3 15 

在每个需要控制逻辑的脚本中。 陷阱确保locking文件在调用者退出时被删除,因此您不必在脚本中的每个退出点上编写该文件。

使用单独的控制脚本意味着您可以完整地检查边缘情况:删除过期的日志文件,validationlocking文件是否与当前正在运行的脚本实例正确关联,是否提供杀死正在运行的进程的选项等等。 这也意味着你有更好的机会在ps输出上成功使用grep。 可以使用ps-grep来validation锁文件是否有与之关联的正在运行的进程。 也许你可以用某种方式来命名你的锁文件,以包含有关进程的信息:user,pid等,以后可以用脚本调用来决定创buildlockfile的进程是否还在。

使用set -o noclobber选项并尝试覆盖通用文件。

一个简短的例子

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

一个更长的例子。 这个例子将等待global.lock,但是等待太长时间后。

  function lockfile_waithold() { declare -ir time_beg=$(date '+%s') declare -ir maxtime=7140 # 7140 s = 1 hour 59 min. # waiting up to ${maxtime}s for /tmp/global.lock ... while ! \ (set -o noclobber ; \ echo -e "DATE:$(date)\nUSER:$(whoami)\nPID:$$" > /tmp/global.lock \ ) 2>/dev/null do if [ $(( $(date '+%s') - ${time_beg})) -gt ${maxtime} ] ; then echo "waited too long for /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 # ...remainder of script lockfile_release 

@Barry Kelly 从这里转发。

第一个testing例子

 [[ $(lsof -t $0| wc -l) > 1 ]] && echo "At least one of $0 is running" 

第二个testing例子

 currsh=$0 currpid=$$ runpid=$(lsof -t $currsh| paste -s -d " ") if [[ $runpid == $currpid ]] then sleep 11111111111111111 else echo -e "\nPID($runpid)($currpid) ::: At least one of \"$currsh\" is running !!!\n" false exit 1 fi 

说明

“lsof -t”列出名为“$ 0”的当前运行脚本的所有pid。

命令“lsof”将会有两个好处。

  1. 忽略由编辑器(如vim)编辑的pid,因为vim编辑它的映射文件,例如“.file.swp”。
  2. 忽略当前正在运行的shell脚本所分配的pid,这是大多数“grep”派生命令无法实现的。 使用“pstree -pH pidnum”命令查看有关当前进程分叉状态的详细信息。

Ubuntu / Debian发行版具有start-stop-daemon工具,它与您描述的目的相同。 另请参阅/etc/init.d/skeleton ,了解如何使用它来编写启动/停止脚本。

– 诺亚

我也build议看看chpst ( runit的一部分):

 chpst -L /tmp/your-lockfile.loc ./script.name.sh 

一行最终解决scheme:

 [ "$(pgrep -fn $0)" -ne "$(pgrep -fo $0)" ] && echo "At least 2 copies of $0 are running" 

我发现这在procmail包依赖关系:

apt install liblockfile-bin

运行: dotlockfile -l file.lock

file.lock将被创build。

解锁: dotlockfile -u file.lock

使用这个来列出这个包文件/命令: dpkg-query -L liblockfile-bin

我有同样的问题,并提出了一个使用lockfile的模板 ,一个保存进程id号的pid文件,以及一个kill -0 $(cat $pid_file)检查,以使被中止的脚本不会停止下一次运行。 这将在/ tmp中创build一个foobar- $ USERID文件夹,其中的lockfile和pid文件位于其中。

您仍然可以调用脚本并执行其他操作,只要您将这些操作保持在alertRunningPSalertRunningPS

 #!/bin/bash user_id_num=$(id -u) pid_file="/tmp/foobar-$user_id_num/foobar-$user_id_num.pid" lock_file="/tmp/foobar-$user_id_num/running.lock" ps_id=$$ function alertRunningPS () { local PID=$(cat "$pid_file" 2> /dev/null) echo "Lockfile present. ps id file: $PID" echo "Checking if process is actually running or something left over from crash..." if kill -0 $PID 2> /dev/null; then echo "Already running, exiting" exit 1 else echo "Not running, removing lock and continuing" rm -f "$lock_file" lockfile -r 0 "$lock_file" fi } echo "Hello, checking some stuff before locking stuff" # Lock further operations to one process mkdir -p /tmp/foobar-$user_id_num lockfile -r 0 "$lock_file" || alertRunningPS # Do stuff here echo -n $ps_id > "$pid_file" echo "Running stuff in ONE ps" sleep 30s rm -f "$lock_file" rm -f "$pid_file" exit 0 

从你的脚本:

 ps -ef | grep $0 | grep $(whoami) 

我发现了一个非常简单的方法来处理“每个系统的脚本的一个副本”。 它不允许我从多个帐户运行多个脚本(在标准的Linux上)。

解:

在剧本的开始,我给了:

 pidof -s -o '%PPID' -x $( basename $0 ) > /dev/null 2>&1 && exit 

显然, pidof的工作方式是:

  • 它没有限制程序名称,如ps -C ...
  • 它不需要我做grep -v grep (或者其他类似的东西)

而且它不依赖于lockfiles,对我来说这是一个很大的胜利,因为在它们上传递意味着你必须添加对陈旧的lockfiles的处理 – 这并不复杂,但是如果可以避免的话 – 为什么不呢?

至于检查“每个正在运行的用户的脚本的一个副本”,我写了这个,但我并不太满意:

 ( pidof -s -o '%PPID' -x $( basename $0 ) | tr ' ' '\n' ps xo pid= | tr -cd '[0-9\n]' ) | sort | uniq -d 

然后检查它的输出 – 如果它是空的 – 没有来自同一用户的脚本副本。

这是我们的标准位。 它可以从脚本以某种方式恢复而不清理它的lockfile。

它将进程ID写入locking文件,如果它正常运行。 如果它在开始运行时发现一个锁文件,它将从锁文件读取进程ID并检查该进程是否存在。 如果进程不存在,它将删除旧的locking文件并继续。 只有当锁文件存在并且进程仍在运行时才会退出。 它在退出时写入一条消息。

 # lock to ensure we don't get two copies of the same job script_name="myscript.sh" lock="/var/run/${script_name}.pid" if [[ -e "${lock}" ]]; then pid=$(cat ${lock}) if [[ -e /proc/${pid} ]]; then echo "${script_name}: Process ${pid} is still running, exiting." exit 1 else # Clean up previous lock file rm -f ${lock} fi fi trap "rm -f ${lock}; exit $?" INT TERM EXIT # write $$ (PID) to the lock file echo "$$" > ${lock}