确保只有一个Bash脚本的一个实例正在运行的最佳方法是什么?
确保给定脚本的一个实例正在运行的最简单/最好的方法是什么?假设它是Linux上的Bash?
目前我正在做:
ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh
但它有几个问题:
- 它把脚本之外的检查
- 它不会让我从单独的帐户运行相同的脚本 – 我有时会这样做。
-
-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 == EXIT
或0
,陷印特定的信号是多余的)。
在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”将会有两个好处。
- 忽略由编辑器(如vim)编辑的pid,因为vim编辑它的映射文件,例如“.file.swp”。
- 忽略当前正在运行的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文件位于其中。
您仍然可以调用脚本并执行其他操作,只要您将这些操作保持在alertRunningPS
中alertRunningPS
。
#!/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}