有什么快速而简单的方法可以确保在给定时间内只有一个shell脚本实例在运行?
当前回答
我对现有的答案有以下问题:
Some answers try to clean up lock files and then having to deal with stale lock files caused by e.g. sudden crash/reboot. IMO that is unnecessarily complicated. Let lock files stay. Some answers use script file itself $0 or $BASH_SOURCE for locking often referring to examples from man flock. This fails when script is replaced due to update or edit causing next run to open and obtain lock on the new script file even though another instance holding a lock on the removed file is still running. Few answers use a fixed file descriptor. This is not ideal. I do not want to rely on how this will behave e.g. opening lock file fails but gets mishandled and attempts to lock on unrelated file descriptor inherited from parent process. Another fail case is injecting locking wrapper for a 3rd party binary that does not handle locking itself but fixed file descriptors can interfere with file descriptor passing to child processes. I reject answers using process lookup for already running script name. There are several reasons for it, such as but not limited to reliability/atomicity, parsing output, and having script that does several related functions some of which do not require locking.
这个答案是:
rely on flock because it gets kernel to provide locking ... provided lock file is created atomically and not replaced. assume and rely on lock file being stored on the local filesystem as opposed to NFS. change lock file presence to NOT mean anything about a running instance. Its role is purely to prevent two concurrent instances creating file with same name and replacing another's copy. Lock file does not get deleted, it gets left behind and can survive across reboots. The locking is indicated via flock not via lock file presence. assume bash shell, as tagged by the question.
它不是一个联机程序,但是没有注释和错误消息,它足够小:
#!/bin/bash
LOCKFILE=/var/lock/TODO
set -o noclobber
exec {lockfd}<> "${LOCKFILE}" || exit 1
set +o noclobber # depends on what you need
flock --exclusive --nonblock ${lockfd} || exit 1
但我更喜欢注释和错误消息:
#!/bin/bash
# TODO Set a lock file name
LOCKFILE=/var/lock/myprogram.lock
# Set noclobber option to ensure lock file is not REPLACED.
set -o noclobber
# Open lock file for R+W on a new file descriptor
# and assign the new file descriptor to "lockfd" variable.
# This does NOT obtain a lock but ensures the file exists and opens it.
exec {lockfd}<> "${LOCKFILE}" || {
echo "pid=$$ failed to open LOCKFILE='${LOCKFILE}'" 1>&2
exit 1
}
# TODO!!!! undo/set the desired noclobber value for the remainder of the script
set +o noclobber
# Lock on the allocated file descriptor or fail
# Adjust flock options e.g. --noblock as needed
flock --exclusive --nonblock ${lockfd} || {
echo "pid=$$ failed to obtain lock fd='${lockfd}' LOCKFILE='${LOCKFILE}'" 1>&2
exit 1
}
# DO work here
echo "pid=$$ obtained exclusive lock fd='${lockfd}' LOCKFILE='${LOCKFILE}'"
# Can unlock after critical section and do more work after unlocking
#flock -u ${lockfd};
# if unlocking then might as well close lockfd too
#exec {lockfd}<&-
其他回答
我对现有的答案有以下问题:
Some answers try to clean up lock files and then having to deal with stale lock files caused by e.g. sudden crash/reboot. IMO that is unnecessarily complicated. Let lock files stay. Some answers use script file itself $0 or $BASH_SOURCE for locking often referring to examples from man flock. This fails when script is replaced due to update or edit causing next run to open and obtain lock on the new script file even though another instance holding a lock on the removed file is still running. Few answers use a fixed file descriptor. This is not ideal. I do not want to rely on how this will behave e.g. opening lock file fails but gets mishandled and attempts to lock on unrelated file descriptor inherited from parent process. Another fail case is injecting locking wrapper for a 3rd party binary that does not handle locking itself but fixed file descriptors can interfere with file descriptor passing to child processes. I reject answers using process lookup for already running script name. There are several reasons for it, such as but not limited to reliability/atomicity, parsing output, and having script that does several related functions some of which do not require locking.
这个答案是:
rely on flock because it gets kernel to provide locking ... provided lock file is created atomically and not replaced. assume and rely on lock file being stored on the local filesystem as opposed to NFS. change lock file presence to NOT mean anything about a running instance. Its role is purely to prevent two concurrent instances creating file with same name and replacing another's copy. Lock file does not get deleted, it gets left behind and can survive across reboots. The locking is indicated via flock not via lock file presence. assume bash shell, as tagged by the question.
它不是一个联机程序,但是没有注释和错误消息,它足够小:
#!/bin/bash
LOCKFILE=/var/lock/TODO
set -o noclobber
exec {lockfd}<> "${LOCKFILE}" || exit 1
set +o noclobber # depends on what you need
flock --exclusive --nonblock ${lockfd} || exit 1
但我更喜欢注释和错误消息:
#!/bin/bash
# TODO Set a lock file name
LOCKFILE=/var/lock/myprogram.lock
# Set noclobber option to ensure lock file is not REPLACED.
set -o noclobber
# Open lock file for R+W on a new file descriptor
# and assign the new file descriptor to "lockfd" variable.
# This does NOT obtain a lock but ensures the file exists and opens it.
exec {lockfd}<> "${LOCKFILE}" || {
echo "pid=$$ failed to open LOCKFILE='${LOCKFILE}'" 1>&2
exit 1
}
# TODO!!!! undo/set the desired noclobber value for the remainder of the script
set +o noclobber
# Lock on the allocated file descriptor or fail
# Adjust flock options e.g. --noblock as needed
flock --exclusive --nonblock ${lockfd} || {
echo "pid=$$ failed to obtain lock fd='${lockfd}' LOCKFILE='${LOCKFILE}'" 1>&2
exit 1
}
# DO work here
echo "pid=$$ obtained exclusive lock fd='${lockfd}' LOCKFILE='${LOCKFILE}'"
# Can unlock after critical section and do more work after unlocking
#flock -u ${lockfd};
# if unlocking then might as well close lockfd too
#exec {lockfd}<&-
PID和锁文件绝对是最可靠的。当您尝试运行程序时,它可以检查锁文件,如果它存在,它可以使用ps查看进程是否仍在运行。如果不是,脚本可以启动,将锁文件中的PID更新为自己的PID。
如果您不想或不能使用flock(例如,您没有使用共享文件系统),请考虑使用外部服务,如lockable。
它暴露了咨询锁原语,就像flock一样。特别地,你可以通过以下方式获取锁:
https://lockable.dev/api/acquire/my-lock-name
然后通过
https://lockable.dev/api/release/my-lock-name
通过将脚本执行与锁获取和释放结合在一起,您可以确保在任何给定时间只有一个流程实例在运行。
我发现bmdhack的解决方案是最实用的,至少对我的用例来说是这样。使用flock和lockfile依赖于在脚本终止时使用rm删除lockfile,这不能总是得到保证(例如,kill -9)。
关于bmdhack的解决方案,我要改变一件小事:它强调删除锁文件,但没有说明这对于这个信号量的安全工作是不必要的。他使用kill -0来确保死进程的旧锁文件将被忽略/覆盖。
因此,我的简化解决方案是简单地将以下内容添加到单例的顶部:
## Test the lock
LOCKFILE=/tmp/singleton.lock
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
echo "Script already running. bye!"
exit
fi
## Set the lock
echo $$ > ${LOCKFILE}
当然,这个脚本仍然有一个缺陷,即可能同时启动的进程存在竞争风险,因为锁定测试和设置操作不是单个原子动作。但是lhunath提出的使用mkdir的解决方案有一个缺陷,即被杀死的脚本可能会留下目录,从而阻止其他实例运行。
这将工作,如果你的脚本名称是唯一的:
#!/bin/bash
if [ $(pgrep -c $(basename $0)) -gt 1 ]; then
echo $(basename $0) is already running
exit 0
fi
如果scriptname不是唯一的,这在大多数linux发行版上都有效:
#!/bin/bash
exec 9>/tmp/my_lock_file
if ! flock -n 9 ; then
echo "another instance of this script is already running";
exit 1
fi
来源:http://mywiki.wooledge.org/BashFAQ/045