有什么快速而简单的方法可以确保在给定时间内只有一个shell脚本实例在运行?
当前回答
当目标是Debian机器时,我发现lockfile-progs包是一个很好的解决方案。Procmail还附带了一个锁文件工具。然而,有时这两种情况我都无法解决。
下面是我的解决方案,它使用mkdir来检测原子性,并使用PID文件来检测过期的锁。这段代码目前在Cygwin安装环境中运行,运行良好。
要使用它,当您需要独占访问某些东西时,只需调用exclusive_lock_require。一个可选的锁名参数允许您在不同的脚本之间共享锁。如果需要更复杂的功能,还有两个较低级别的函数(exclusive_lock_try和exclusive_lock_retry)。
function exclusive_lock_try() # [lockname]
{
local LOCK_NAME="${1:-`basename $0`}"
LOCK_DIR="/tmp/.${LOCK_NAME}.lock"
local LOCK_PID_FILE="${LOCK_DIR}/${LOCK_NAME}.pid"
if [ -e "$LOCK_DIR" ]
then
local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
if [ ! -z "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2> /dev/null
then
# locked by non-dead process
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
return 1
else
# orphaned lock, take it over
( echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null && local LOCK_PID="$$"
fi
fi
if [ "`trap -p EXIT`" != "" ]
then
# already have an EXIT trap
echo "Cannot get lock, already have an EXIT trap"
return 1
fi
if [ "$LOCK_PID" != "$$" ] &&
! ( umask 077 && mkdir "$LOCK_DIR" && umask 177 && echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null
then
local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
# unable to acquire lock, new process got in first
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
return 1
fi
trap "/bin/rm -rf \"$LOCK_DIR\"; exit;" EXIT
return 0 # got lock
}
function exclusive_lock_retry() # [lockname] [retries] [delay]
{
local LOCK_NAME="$1"
local MAX_TRIES="${2:-5}"
local DELAY="${3:-2}"
local TRIES=0
local LOCK_RETVAL
while [ "$TRIES" -lt "$MAX_TRIES" ]
do
if [ "$TRIES" -gt 0 ]
then
sleep "$DELAY"
fi
local TRIES=$(( $TRIES + 1 ))
if [ "$TRIES" -lt "$MAX_TRIES" ]
then
exclusive_lock_try "$LOCK_NAME" > /dev/null
else
exclusive_lock_try "$LOCK_NAME"
fi
LOCK_RETVAL="${PIPESTATUS[0]}"
if [ "$LOCK_RETVAL" -eq 0 ]
then
return 0
fi
done
return "$LOCK_RETVAL"
}
function exclusive_lock_require() # [lockname] [retries] [delay]
{
if ! exclusive_lock_retry "$@"
then
exit 1
fi
}
其他回答
使用flock(1)在文件描述符上创建一个独占作用域锁。通过这种方式,您甚至可以同步脚本的不同部分。
#!/bin/bash
(
# Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
flock -x -w 10 200 || exit 1
# Do stuff
) 200>/var/lock/.myscript.exclusivelock
这确保(和)之间的代码一次只由一个进程运行,并且该进程不会为锁等待太长时间。
注意:这个特殊的命令是util-linux的一部分。如果您运行的是Linux以外的操作系统,那么它可能不可用,也可能不可用。
信号量实用程序使用flock(如上所述,例如by presto8)来实现计数信号量。它支持您想要的任意数量的并发进程。我们使用它来限制各种队列工作者进程的并发级别。
它很像sem,但重量轻得多。(完全披露:我是在发现sem对我们的需求来说太繁重了,而且没有一个简单的计数信号量实用程序可用后写的。)
下面是一个使用锁文件并将PID回显到锁文件中的实现。如果进程在删除pidfile之前被杀死,这可以起到保护作用:
LOCKFILE=/tmp/lock.txt
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
echo "already running"
exit
fi
# make sure the lockfile is removed when we exit and then claim it
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}
# do stuff
sleep 1000
rm -f ${LOCKFILE}
这里的技巧是kill -0,它不传递任何信号,只是检查具有给定PID的进程是否存在。此外,调用trap将确保即使进程被杀死(kill -9除外),锁文件也会被移除。
在脚本的开头添加这一行
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
这是人类群体的样板代码。
如果需要更多的日志记录,可以使用这个
[ "${FLOCKER}" != "$0" ] && { echo "Trying to start build from queue... "; exec bash -c "FLOCKER='$0' flock -E $E_LOCKED -en '$0' '$0' '$@' || if [ \"\$?\" -eq $E_LOCKED ]; then echo 'Locked.'; fi"; } || echo "Lock is free. Completing."
使用flock工具设置和检查锁。 这段代码通过检查FLOCKER变量来检测它是否第一次运行,如果它没有设置为脚本名称,那么它会尝试再次递归地使用flock启动脚本,并初始化FLOCKER变量,如果FLOCKER设置正确,那么在前一次迭代中flock成功,可以继续。如果锁繁忙,它将失败,并使用可配置的退出代码。
它似乎不能在Debian 7上工作,但似乎可以在实验util-linux 2.25包上再次工作。上面写着“羊群:……文本文件繁忙”。可以通过禁用脚本上的写权限来覆盖它。
又快又脏?脚本顶部的一行代码可以工作:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
当然,只要确保您的脚本名称是唯一的。:)