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


当前回答

又快又脏?脚本顶部的一行代码可以工作:

[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit

当然,只要确保您的脚本名称是唯一的。:)

其他回答

信号量实用程序使用flock(如上所述,例如by presto8)来实现计数信号量。它支持您想要的任意数量的并发进程。我们使用它来限制各种队列工作者进程的并发级别。

它很像sem,但重量轻得多。(完全披露:我是在发现sem对我们的需求来说太繁重了,而且没有一个简单的计数信号量实用程序可用后写的。)

对于shell脚本,我倾向于使用mkdir而不是flock,因为它使锁更可移植。

不管怎样,使用set -e是不够的。它只在任何命令失败时退出脚本。你的锁还是会留下的。

为了正确的锁清理,你真的应该把你的陷阱设置成这样的伪代码(提取,简化和未经测试,但来自积极使用的脚本):

#=======================================================================
# Predefined Global Variables
#=======================================================================

TMPDIR=/tmp/myapp
[[ ! -d $TMP_DIR ]] \
    && mkdir -p $TMP_DIR \
    && chmod 700 $TMPDIR

LOCK_DIR=$TMP_DIR/lock

#=======================================================================
# Functions
#=======================================================================

function mklock {
    __lockdir="$LOCK_DIR/$(date +%s.%N).$$" # Private Global. Use Epoch.Nano.PID

    # If it can create $LOCK_DIR then no other instance is running
    if $(mkdir $LOCK_DIR)
    then
        mkdir $__lockdir  # create this instance's specific lock in queue
        LOCK_EXISTS=true  # Global
    else
        echo "FATAL: Lock already exists. Another copy is running or manually lock clean up required."
        exit 1001  # Or work out some sleep_while_execution_lock elsewhere
    fi
}

function rmlock {
    [[ ! -d $__lockdir ]] \
        && echo "WARNING: Lock is missing. $__lockdir does not exist" \
        || rmdir $__lockdir
}

#-----------------------------------------------------------------------
# Private Signal Traps Functions {{{2
#
# DANGER: SIGKILL cannot be trapped. So, try not to `kill -9 PID` or 
#         there will be *NO CLEAN UP*. You'll have to manually remove 
#         any locks in place.
#-----------------------------------------------------------------------
function __sig_exit {

    # Place your clean up logic here 

    # Remove the LOCK
    [[ -n $LOCK_EXISTS ]] && rmlock
}

function __sig_int {
    echo "WARNING: SIGINT caught"    
    exit 1002
}

function __sig_quit {
    echo "SIGQUIT caught"
    exit 1003
}

function __sig_term {
    echo "WARNING: SIGTERM caught"    
    exit 1015
}

#=======================================================================
# Main
#=======================================================================

# Set TRAPs
trap __sig_exit EXIT    # SIGEXIT
trap __sig_int INT      # SIGINT
trap __sig_quit QUIT    # SIGQUIT
trap __sig_term TERM    # SIGTERM

mklock

# CODE

exit # No need for cleanup code here being in the __sig_exit trap function

接下来会发生什么。所有陷阱都会产生一个出口,所以__sig_exit函数总是会发生(除非SIGKILL),它会清理你的锁。

注意:我的退出值不是低值。为什么?各种批处理系统生成或期望数字0到31。将它们设置为其他内容,我可以让我的脚本和批处理流对前一个批处理作业或脚本做出相应的反应。

看看FLOM (Free LOck Manager) http://sourceforge.net/projects/flom/:,您可以使用文件系统中不需要锁文件的抽象资源来同步命令和/或脚本。您可以在没有NFS(网络文件系统)服务器这样的NAS(网络附加存储)的情况下同步在不同系统中运行的命令。

使用最简单的用例,序列化“command1”和“command2”可能和执行一样简单:

flom -- command1

and

flom -- command2

来自两个不同的shell脚本。

另一个选项是通过运行set -C来使用shell的noclobber选项。如果文件已经存在,那么>将失败。

简而言之:

set -C
lockfile="/tmp/locktest.lock"
if echo "$$" > "$lockfile"; then
    echo "Successfully acquired lock"
    # do work
    rm "$lockfile"    # XXX or via trap - see below
else
    echo "Cannot acquire lock - already locked by $(cat "$lockfile")"
fi

这会导致shell调用:

open(pathname, O_CREAT|O_EXCL)

自动创建文件,如果文件已经存在则失败。


根据BashFAQ 045上的评论,这可能在ksh88中失败,但它在我所有的shell中都有效:

$ strace -e trace=creat,open -f /bin/bash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/zsh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/pdksh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/dash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

有趣的是pdksh添加了O_TRUNC标志,但显然这是多余的: 要么创建一个空文件,要么什么也不做。


如何进行rm取决于您希望如何处理不干净的出口。

在干净退出时删除

新的运行失败,直到导致不干净退出的问题得到解决,并手动删除锁文件。

# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"

在任何出口删除

只要脚本尚未运行,新的运行就会成功。

trap 'rm "$lockfile"' EXIT

一个有flock(1)但没有subshell的例子。Flock ()ed文件/tmp/foo永远不会被删除,但这没关系,因为它会被Flock()和un-flock()ed。

#!/bin/bash

exec 9<> /tmp/foo
flock -n 9
RET=$?
if [[ $RET -ne 0 ]] ; then
    echo "lock failed, exiting"
    exit
fi

#Now we are inside the "critical section"
echo "inside lock"
sleep 5
exec 9>&- #close fd 9, and release lock

#The part below is outside the critical section (the lock)
echo "lock released"
sleep 5