有什么快速而简单的方法可以确保在给定时间内只有一个shell脚本实例在运行?


当前回答

为了使锁定可靠,您需要一个原子操作。以上许多建议 不是原子的。建议的lockfile(1)实用程序作为手册页看起来很有前途 提到,它是“抗nfs”的。如果您的操作系统不支持lockfile(1)和 您的解决方案必须在NFS上工作,您没有太多的选项....

NFSv2有两个原子操作:

符号链接 重命名

在NFSv3中,create调用也是原子的。

目录操作在NFSv2和NFSv3下不是原子的(请参考Brent Callaghan的书“NFS Illustrated”,ISBN 0-201-32570-5;Brent是Sun的nfs老手)。

知道了这一点,你可以为文件和目录实现自旋锁(在shell中,而不是PHP中):

锁定当前目录:

while ! ln -s . lock; do :; done

锁定文件:

while ! ln -s ${f} ${f}.lock; do :; done

解锁当前目录(假设正在运行的进程真的获得了锁):

mv lock deleteme && rm deleteme

解锁文件(假设正在运行的进程真的获得了锁):

mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme

Remove也不是原子的,因此首先是rename(它是原子的),然后是Remove。

对于符号链接和重命名调用,两个文件名必须驻留在同一个文件系统上。我的建议是:只使用简单的文件名(没有路径),把file和lock放在同一个目录下。

其他回答

羊群走的路才是正确的。想想当脚本突然失效时会发生什么。在羊群的情况下,你只是松散的羊群,但这不是一个问题。另外,请注意,一个邪恶的伎俩是在脚本本身取一群。但这当然会让您完全陷入权限问题。

已经回答了一百万次了,但是另一种方式,不需要外部依赖:

LOCK_FILE="/var/lock/$(basename "$0").pid"
trap "rm -f ${LOCK_FILE}; exit" INT TERM EXIT
if [[ -f $LOCK_FILE && -d /proc/`cat $LOCK_FILE` ]]; then
   // Process already exists
   exit 1
fi
echo $$ > $LOCK_FILE

每次它将当前PID($$)写入锁文件,并在脚本启动时检查进程是否正在使用最新的PID运行。

试试下面的方法,

ab=`ps -ef | grep -v grep | grep -wc processname`

然后使用if循环将变量与1匹配。

您可以使用GNU Parallel,因为它在作为sem调用时是作为互斥量工作的。所以,具体来说,你可以使用:

sem --id SCRIPTSINGLETON yourScript

如果你也想要一个超时,使用:

sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript

如果信号量在超时时间内没有释放,Timeout <0表示退出而不运行脚本,>的Timeout表示仍然运行脚本。

注意,您应该给它一个名称(使用——id),否则它默认为控制终端。

GNU Parallel在大多数Linux/OSX/Unix平台上是一个非常简单的安装程序——它只是一个Perl脚本。

测试“锁文件”是否存在的简单方法是有缺陷的。

为什么?因为它们不会检查文件是否存在,也不会在单个原子操作中创建文件。因为这个;有一个竞争条件会使你的互斥失败。

相反,您可以使用mkdir。Mkdir创建一个目录,如果它还不存在,如果它存在,它设置一个退出码。更重要的是,它在一个原子动作中完成了所有这些操作,这使得它非常适合这个场景。

if ! mkdir /tmp/myscript.lock 2>/dev/null; then
    echo "Myscript is already running." >&2
    exit 1
fi

有关所有细节,请参阅优秀的BashFAQ: http://mywiki.wooledge.org/BashFAQ/045

如果你想要处理陈旧的锁,fuser(1)会派上用场。唯一的缺点是这个操作大约需要一秒钟,所以它不是即时的。

下面是我曾经写过的一个函数,它使用fuser解决了这个问题:

#       mutex file
#
# Open a mutual exclusion lock on the file, unless another process already owns one.
#
# If the file is already locked by another process, the operation fails.
# This function defines a lock on a file as having a file descriptor open to the file.
# This function uses FD 9 to open a lock on the file.  To release the lock, close FD 9:
# exec 9>&-
#
mutex() {
    local file=$1 pid pids 

    exec 9>>"$file"
    { pids=$(fuser -f "$file"); } 2>&- 9>&- 
    for pid in $pids; do
        [[ $pid = $$ ]] && continue

        exec 9>&- 
        return 1 # Locked by a pid.
    done 
}

你可以像这样在脚本中使用它:

mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }

如果您不关心可移植性(这些解决方案应该适用于几乎任何UNIX机器),Linux的fuser(1)提供了一些额外的选项,还有flock(1)。