快速而又脏的方法,确保一次只运行一个shell脚本实例

时间:2008-10-09 00:13:26

标签: bash shell process lockfile

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

40 个答案:

答案 0 :(得分:193)

使用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以外的操作系统,它可能会也可能不可用。

答案 1 :(得分:150)

所有测试“锁定文件”存在的方法都存在缺陷。

为什么呢?因为无法检查文件是否存在并在单个原子操作中创建它。因为这;有一种竞争条件,让您尝试互斥。

相反,您需要使用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)会派上用场。这里唯一的缺点是操作需要大约一秒钟,所以它不是即时的。

这是我用过热熔器解决问题的一个函数:

#       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)

答案 2 :(得分:100)

这是一个使用 lockfile 并将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的调用将确保即使您的进程被终止也会删除 lockfile kill -9除外)。

答案 3 :(得分:41)

flock(2)系统调用周围有一个包装器,称为univginative,flock(1)。这使得可靠地获得排他锁而不用担心清理等相对容易。the man page上有关于如何在shell脚本中使用它的例子。

答案 4 :(得分:27)

你需要一个原子操作,比如flock,否则这最终会失败。

但是如果flock不可用怎么办。那么有mkdir。这也是一个原子操作。只有一个流程会导致mkdir成功,其他流程都会失败。

所以代码是:

if mkdir /var/lock/.myscript.exclusivelock
then
  # do stuff
  :
  rmdir /var/lock/.myscript.exclusivelock
fi

你需要处理陈旧的锁定,否则你的脚本将永远不会再次运行。

答案 5 :(得分:24)

为了使锁定可靠,您需要一个原子操作。上述许多建议 不是原子的。建议的lockfile(1)实用程序看起来很有希望成为man-page 提到它,它的“NFS抗性”。如果您的操作系统不支持lockfile(1)和 你的解决方案必须在NFS上工作,你没有太多的选择......

NFSv2有两个原子操作:

  • 符号链接
  • 重命名

使用NFSv3,创建调用也是原子的。

目录操作在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

删除也不是原子的,因此首先重命名(这是原子的)然后删除。

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

答案 6 :(得分:22)

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

简而言之:

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中失败,但它适用于我的所有外壳:

$ 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

答案 7 :(得分:18)

您可以使用GNU Parallel,因为它在调用sem时可用作互斥锁。因此,具体而言,您可以使用:

sem --id SCRIPTSINGLETON yourScript

如果您也想要超时,请使用:

sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript

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

请注意,您应该为其命名(--id),否则默认为控制终端。

在大多数Linux / OSX / Unix平台上,

GNU Parallel是一个非常简单的安装 - 它只是一个Perl脚本。

答案 8 :(得分:16)

对于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有所期望或设想。将它们设置为其他东西,我可以让我的脚本和批处理流对上一个批处理作业或脚本做出相应的反应。

答案 9 :(得分:13)

真的 快速真的脏吗?脚本顶部的这个单行将起作用:

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

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

答案 10 :(得分:5)

在已知位置创建锁定文件并检查脚本启动是否存在?如果有人试图追踪阻止执行脚本的错误实例,那么将PID放在文件中可能会有所帮助。

答案 11 :(得分:5)

这是一种将原子目录锁定与通过PID检查过时锁定并在失效时重启的方法。此外,这不依赖于任何基础。

#!/bin/dash

SCRIPTNAME=$(basename $0)
LOCKDIR="/var/lock/${SCRIPTNAME}"
PIDFILE="${LOCKDIR}/pid"

if ! mkdir $LOCKDIR 2>/dev/null
then
    # lock failed, but check for stale one by checking if the PID is really existing
    PID=$(cat $PIDFILE)
    if ! kill -0 $PID 2>/dev/null
    then
       echo "Removing stale lock of nonexistent PID ${PID}" >&2
       rm -rf $LOCKDIR
       echo "Restarting myself (${SCRIPTNAME})" >&2
       exec "$0" "$@"
    fi
    echo "$SCRIPTNAME is already running, bailing out" >&2
    exit 1
else
    # lock successfully acquired, save PID
    echo $$ > $PIDFILE
fi

trap "rm -rf ${LOCKDIR}" QUIT INT TERM EXIT


echo hello

sleep 30s

echo bye

答案 12 :(得分:5)

这个例子在man flock中有解释,但它需要一些改进,因为我们应该管理bug和退出代码:

   #!/bin/bash
   #set -e this is useful only for very stupid scripts because script fails when anything command exits with status more than 0 !! without possibility for capture exit codes. not all commands exits >0 are failed.

( #start subprocess
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200
  if [ "$?" != "0" ]; then echo Cannot lock!; exit 1; fi
  echo $$>>/var/lock/.myscript.exclusivelock #for backward lockdir compatibility, notice this command is executed AFTER command bottom  ) 200>/var/lock/.myscript.exclusivelock.
  # Do stuff
  # you can properly manage exit codes with multiple command and process algorithm.
  # I suggest throw this all to external procedure than can properly handle exit X commands

) 200>/var/lock/.myscript.exclusivelock   #exit subprocess

FLOCKEXIT=$?  #save exitcode status
    #do some finish commands

exit $FLOCKEXIT   #return properly exitcode, may be usefull inside external scripts

您可以使用其他方法,列出我过去使用的流程。但这种方法比上述方法更复杂。你应该按ps列出进程,按名称过滤,附加过滤器grep -v grep用于删除寄生虫nad最后用grep -c计算它。并与数字进行比较。它复杂而不确定

答案 13 :(得分:4)

在定位Debian机器时,我发现lockfile-progs包是一个很好的解决方案。 procmail还附带lockfile工具。但有时候我不会遇到这些。

这是我的解决方案,它使用mkdir用于原子和PID文件来检测失效锁。此代码目前正在Cygwin设置中生成并且运行良好。

要使用它,只需在需要获得某些内容的独占权限时调用exclusive_lock_require即可。可选的锁定名称参数允许您在不同脚本之间共享锁定。如果您需要更复杂的东西,还有两个较低级别的函数(exclusive_lock_tryexclusive_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
}

答案 14 :(得分:4)

如果flock的限制已经在这个线程的其他地方描述过,对你来说不是问题,那么这应该有用:

#!/bin/bash

{
    # exit if we are unable to obtain a lock; this would happen if 
    # the script is already running elsewhere
    # note: -x (exclusive) is the default
    flock -n 100 || exit

    # put commands to run here
    sleep 100
} 100>/tmp/myjob.lock 

答案 15 :(得分:3)

我想取消锁定文件,锁定,特殊锁定程序甚至pidof,因为在所有Linux安装中都找不到它。也希望有最简单的代码(或至少尽可能少的代码)。最简单的if语句,在一行中:

if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi

答案 16 :(得分:3)

某些unix具有 lockfile ,这与已经提到的 flock 非常相似。

从联系手册:

  

lockfile可用于创建一个   或更多信号量文件。如果锁定 -   文件无法创建所有指定的   它(以指定的顺序),它   等待睡眠时间(默认为8)   秒并重试最后一个文件   没有成功。你可以指定   要重试的次数   失败被退回。如果是这个号码   重试次数为-1(默认值,即   -r-1)lockfile将永远重试。

答案 17 :(得分:2)

实际上虽然bmdhacks的答案几乎是好的,但是在第一次检查锁文件之后和编写它之前,第二个脚本运行的可能性很小。所以他们都会写锁文件,他们都会运行。以下是如何使其正常工作:

lockfile=/var/lock/myscript.lock

if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null ; then
  trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
else
  # or you can decide to skip the "else" part if you want
  echo "Another instance is already running!"
fi

如果文件已存在,noclobber选项将确保重定向命令失败。所以redirect命令实际上是原子的 - 你用一个命令编写并检查文件。您不需要删除文件末尾的锁文件 - 它将被陷阱删除。我希望这对那些稍后会阅读它的人有所帮助。

P.S。我没有看到Mikel已经正确地回答了这个问题,尽管他没有包含trap命令来减少使用Ctrl-C停止脚本后锁定文件遗留的可能性。所以这是完整的解决方案

答案 18 :(得分:2)

我使用一种处理过时锁文件的简单方法。

注意上面存储pid的一些解决方案,忽略了pid可以回绕的事实。所以 - 仅仅检查存储的pid是否存在有效进程是不够的,特别是对于长时间运行的脚本。

我使用noclobber确保一次只能打开一个脚本并写入锁文件。此外,我存储了足够的信息来唯一地标识锁文件中的进程。我定义了一组数据来唯一地标识一个进程为pid,ppid,lstart的进程。

当新脚本启动时,如果它无法创建锁定文件,则会验证创建锁定文件的进程是否仍然存在。如果没有,我们假设原始过程死于非正常死亡,并留下陈旧的锁定文件。然后,新脚本取得了锁定文件的所有权,而且一切都很好。

应该跨多个平台使用多个shell。快速,便携,简单。

#!/usr/bin/env sh
# Author: rouble

LOCKFILE=/var/tmp/lockfile #customize this line

trap release INT TERM EXIT

# Creates a lockfile. Sets global variable $ACQUIRED to true on success.
# 
# Returns 0 if it is successfully able to create lockfile.
acquire () {
    set -C #Shell noclobber option. If file exists, > will fail.
    UUID=`ps -eo pid,ppid,lstart $$ | tail -1`
    if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
        ACQUIRED="TRUE"
        return 0
    else
        if [ -e $LOCKFILE ]; then 
            # We may be dealing with a stale lock file.
            # Bring out the magnifying glass. 
            CURRENT_UUID_FROM_LOCKFILE=`cat $LOCKFILE`
            CURRENT_PID_FROM_LOCKFILE=`cat $LOCKFILE | cut -f 1 -d " "`
            CURRENT_UUID_FROM_PS=`ps -eo pid,ppid,lstart $CURRENT_PID_FROM_LOCKFILE | tail -1`
            if [ "$CURRENT_UUID_FROM_LOCKFILE" == "$CURRENT_UUID_FROM_PS" ]; then 
                echo "Script already running with following identification: $CURRENT_UUID_FROM_LOCKFILE" >&2
                return 1
            else
                # The process that created this lock file died an ungraceful death. 
                # Take ownership of the lock file.
                echo "The process $CURRENT_UUID_FROM_LOCKFILE is no longer around. Taking ownership of $LOCKFILE"
                release "FORCE"
                if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
                    ACQUIRED="TRUE"
                    return 0
                else
                    echo "Cannot write to $LOCKFILE. Error." >&2
                    return 1
                fi
            fi
        else
            echo "Do you have write permissons to $LOCKFILE ?" >&2
            return 1
        fi
    fi
}

# Removes the lock file only if this script created it ($ACQUIRED is set), 
# OR, if we are removing a stale lock file (first parameter is "FORCE") 
release () {
    #Destroy lock file. Take no prisoners.
    if [ "$ACQUIRED" ] || [ "$1" == "FORCE" ]; then
        rm -f $LOCKFILE
    fi
}

# Test code
# int main( int argc, const char* argv[] )
echo "Acquring lock."
acquire
if [ $? -eq 0 ]; then 
    echo "Acquired lock."
    read -p "Press [Enter] key to release lock..."
    release
    echo "Released lock."
else
    echo "Unable to acquire lock."
fi

答案 19 :(得分:2)

在脚本开头添加此行

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :

这是来自man flock的样板代码。

如果您想要更多日志记录,请使用此

[ "${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软件包。它写道:“flock:......文本文件繁忙”。可以通过禁用脚本的写入权限来覆盖它。

答案 20 :(得分:2)

发布的现有答案要么依赖于CLI实用程序flock,要么不能正确保护锁定文件。 flock实用程序并非在所有非Linux系统(即FreeBSD)上都可用,并且在NFS上无法正常工作。

在我的系统管理和系统开发的早期阶段,我被告知创建锁文件的安全且相对便携的方法是使用mkemp(3)mkemp(1)创建临时文件,写入标识信息到临时文件(即PID),然后将临时文件硬链接到锁文件。如果链接成功,那么您已成功获得锁定。

在shell脚本中使用锁时,我通常会在共享配置文件中放置obtain_lock()函数,然后从脚本中获取它。下面是我的锁定功能的一个例子:

obtain_lock()
{
  LOCK="${1}"
  LOCKDIR="$(dirname "${LOCK}")"
  LOCKFILE="$(basename "${LOCK}")"

  # create temp lock file
  TMPLOCK=$(mktemp -p "${LOCKDIR}" "${LOCKFILE}XXXXXX" 2> /dev/null)
  if test "x${TMPLOCK}" == "x";then
     echo "unable to create temporary file with mktemp" 1>&2
     return 1
  fi
  echo "$$" > "${TMPLOCK}"

  # attempt to obtain lock file
  ln "${TMPLOCK}" "${LOCK}" 2> /dev/null
  if test $? -ne 0;then
     rm -f "${TMPLOCK}"
     echo "unable to obtain lockfile" 1>&2
     if test -f "${LOCK}";then
        echo "current lock information held by: $(cat "${LOCK}")" 1>&2
     fi
     return 2
  fi
  rm -f "${TMPLOCK}"

  return 0;
};

以下是如何使用锁定功能的示例:

#!/bin/sh

. /path/to/locking/profile.sh
PROG_LOCKFILE="/tmp/myprog.lock"

clean_up()
{
  rm -f "${PROG_LOCKFILE}"
}

obtain_lock "${PROG_LOCKFILE}"
if test $? -ne 0;then
   exit 1
fi
trap clean_up SIGHUP SIGINT SIGTERM

# bulk of script

clean_up
exit 0
# end of script

请务必在脚本的任何退出点拨打clean_up

我在Linux和FreeBSD环境中都使用了上述功能。

答案 21 :(得分:1)

我发现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提出的解决方案有一个缺陷,即一个被杀死的脚本可能会留下目录,从而阻止其他实例运行。

答案 22 :(得分:1)

PID和锁定文件绝对是最可靠的。当您尝试运行该程序时,它可以检查锁定文件,如果存在,则可以使用ps查看该进程是否仍在运行。如果不是,则脚本可以启动,将锁定文件中的PID更新为自己的。

答案 23 :(得分:1)

使用进程的锁要强大得多,并且还要处理不舒适的出口。 只要进程正在运行,lock_file就会保持打开状态。一旦进程存在(即使进程被杀死),它将被shell关闭(通过shell)。 我发现这非常有效:

lock_file=/tmp/`basename $0`.lock

if fuser $lock_file > /dev/null 2>&1; then
    echo "WARNING: Other instance of $(basename $0) running."
    exit 1
fi
exec 3> $lock_file 

答案 24 :(得分:1)

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

import React, { Component } from 'react';
import moment from 'moment';

import $ from 'jquery';
import axios from 'axios';

import {

  Link
} from 'react-router-dom';

import blogTopImage from '../../images/blog2.png';


var divStyle;

divStyle = {
  backgroundImage: "url("  + blogTopImage + ")" ,
  backgroundPosition: 'center center',
  backgroundRepeat:'no-repeat',            
 backgroundSize: 'contain',
 height: '300px',
}


class Blog extends Component {
 constructor(props) {
   super()

this.state = {

  data :[],
  firstName:'',
        lastName:'',
        email:'',
}
     this.handleFirstNameChange = 
this.handleFirstNameChange.bind(this);
  this.handleLastNameChange = 
this.handleLastNameChange.bind(this);
  this.handleEmailChange = this.handleEmailChange.bind(this);


}



handleFirstNameChange = (e) => {
  this.setState({firstName:e.target.value})
}


handleLastNameChange = (e) => {
  this.setState({lastName:e.target.value})
}

handleEmailChange = (e) => {
  this.setState({email:e.target.value})
}



enter = (e) => {




  axios.post('../api/newsletter', {
    firstName: this.state.firstName,
    lastName: this.state.lastName,
    email: this.state.email,

  })
  .then(function (response) {
    console.log('response from add post is ', response);

  })
  .catch(function (error) {
    console.log(error);
  });


  }



componentDidMount = () =>{

    axios.get(`../api/check`)
    .then(res => {
      var data = res.data;
       this.setState({ data });
    })



  $(document).ready(function(){

  $('.container').fadeIn(3000);

  });

}


 render() {

    return (

  <main>
<div class="wik31">
<div style={divStyle} >


</div>

</div>



  <div className="container">




  <div className="space6"></div>

  <div className=" row">




    {this.state.data.map(data => 
            <Link to={'/page/' + data._id}>


    <div className=" bog col s12 m6 l4 blog-container" style= 
   {{

backgroundImage: "url(" + data.postImage + ")" ,
backgroundPosition: "center center",
backgroundRepeat: "no-repeat",
backgroundSize: "cover",
height: '350px',



    }}>


     <div className="after" >
     <div className="blog_title">
     <p class="wik28"> {data.title}</p>
     </div>
     <div className="blog_date">

     <p> 
     {moment(data.timestamp).format('ll')}
    </p>

     </div>
     </div>



 </div>
 </Link>

 )}





    </div>

  <div className="space2"></div>

<form>
 <h4 className="wik7  center">Sign up for The Peel</h4>
    <div className="space6"></div>

     <div id="notif-container">

     <div className="input-field  notif-item1">
    <input type="text" id="inputFirstName"  onChange= 
{this.handleFirstNameChange.bind(this)} placeholder="First 
Name" required className="validate  tat"/>

  </div>

     <div className="input-field  notif-item1">
    <input type="text" id="inputLastName"  onChange= 
   {this.handleLastNameChange.bind(this)} placeholder="Last 
   Name" required className="validate  tat"/>
    </div>


  <div className="input-field notif-item2">
    <input type="text" id="inputEmail"  onChange= 
{this.handleEmailChange.bind(this)} placeholder="Email" 
 required className="validate  tat"/>
  </div>

  <div className="notif-item3">
                                <button className="btn btn-large 
BottonColor"onClick={this.enter.bind(this)}  type="submit" 
style = {{width:'100%'}}>
                                    subscribe
                                </button>


                            </div>



</div>

</form>
<div className="space2b"></div>




</div>
   <div className="space5"></div>
  </main>


    );
  }



}



export default Blog;

每次将当前PID($$)写入lockfile并在脚本启动时检查进程是否正在运行最新的PID。

答案 25 :(得分:1)

semaphoric实用程序使用flock(如上所述,例如通过presto8)来实现counting semaphore。它支持您想要的任何特定数量的并发进程。我们使用它来限制各种队列工作进程的并发级别。

它类似于sem,但很多重量轻。 (完全披露:我发现sem对我们的需求来说太沉重了,并且没有一个简单的计数信号量实用程序可用。)

答案 26 :(得分:1)

flock(1)但没有子shell的示例。 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

答案 27 :(得分:0)

我没有在任何地方找到它,它使用读取,我不知道读取是否实际上是原子的,但它到目前为止对我有好处...,它很多汁,因为它只是bash builtins,这是一个进程实现,你启动了locker协同进程并使用它的i / o来管理锁,只需将目标i / o从locker文件描述符交换到on文件系统文件描述符({{ 1}})

rg=cells(1,1)

答案 28 :(得分:0)

这一行答案来自相关人员Ask Ubuntu Q&A

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
#     This is useful boilerplate code for shell scripts.  Put it at the top  of
#     the  shell script you want to lock and it'll automatically lock itself on
#     the first run.  If the env var $FLOCKER is not set to  the  shell  script
#     that  is being run, then execute flock and grab an exclusive non-blocking
#     lock (using the script itself as the lock file) before re-execing  itself
#     with  the right arguments.  It also sets the FLOCKER env var to the right
#     value so it doesn't run again.

答案 29 :(得分:0)

我在脚本的开头使用了oneliner:

#!/bin/bash

if [[ $(pgrep -afc "$(basename "$0")") -gt "1" ]]; then echo "Another instance of "$0" has already been started!" && exit; fi
.
the_beginning_of_actual_script

很高兴看到内存中存在进程(无论进程的状态如何);但这对我有用。

答案 30 :(得分:0)

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

使用最简单的用例,序列化&#34; command1&#34;和&#34; command2&#34;可能就像执行一样简单:

flom -- command1

flom -- command2

来自两个不同的shell脚本。

答案 31 :(得分:0)

在晚会上,使用@Majal的想法,这是我的脚本,只启动一个emacsclient GUI实例。有了它,我可以设置快捷键来打开或跳回到同一个emacsclient。我有另一个脚本在我需要时在终端中调用emacsclient。这里使用emacsclient只是为了展示一个工作示例,可以选择别的东西。对于我的小脚本,这种方法快速而且足够好。告诉我脏的地方:)。

#!/bin/bash

# if [ $(pgrep -c $(basename $0)) -lt 2 ]; then # this works but requires script name to be unique
if [ $(pidof -x "$0"|wc -w ) -lt 3 ]; then
    echo -e "Starting $(basename $0)"
    emacsclient --alternate-editor="" -c "$@"
else
    echo -e "$0 is running already"
fi

答案 32 :(得分:0)

我有一个基于文件名的简单解决方案

#!/bin/bash

MY_FILENAME=`basename "$BASH_SOURCE"`

MY_PROCESS_COUNT=$(ps a -o pid,cmd | grep $MY_FILENAME | grep -v grep | grep -v $$ | wc -
l)

if [ $MY_PROCESS_COUNT -ne 0  ]; then
  echo found another process
  exit 0
if

# Follows the code to get the job done.

答案 33 :(得分:0)

    jQuery(document).ready(function( $ ) {
    var bar = $('#wpresmenu_bar'), //top bar that shows/hides the menu
    bar_height = bar.outerHeight(true), //the bar height
    from_width = wpresmenu.from_width,
    menu = $('#wpresmenu_menu'), //the menu div
    menu_ul = $('#wpresmenu_menu_ul'), //the menu ul
    menu_a = menu.find('a'), //single menu link
    body = $('body'),
    html = $('html'),
    animation_speed = 300,
    ab = $('#wpadminbar'),
    menu_enabled = (bar.length > 0 && menu.length > 0)? true : false,
    menu_width = menu.width(),
    target_height = (window.innerHeight < body.height())? body.height() : window.innerHeight,
    target_width = (window.innerWidth < body.width())? body.width() : window.innerWidth;
    if(menu_enabled) {      
    menu_ul.find('li').first().css({'border-top':'none'});
    $(document).mouseup(function (e) {
        if ( !menu.is(e.target) && menu.has( e.target ).length === 0) {
            if(menu.is(':visible') && (!menu.hasClass('top'))) {
                $.sidr('close', 'wpresmenu_menu');
            }
        }
    });
    //ENABLE NESTING

    //add arrow element to the parent li items and chide its child uls
    menu.find('ul.sub-menu').each(function() {
            var sub_ul = $(this),
            parent_a = sub_ul.prev('a'),
            parent_li = parent_a.parent('li').first();

        parent_a.addClass('wpresmenu_parent_item');
        parent_li.addClass('wpresmenu_parent_item_li');

        var expand = parent_a.before('<span class="wpresmenu_icon wpresmenu_icon_par icon_default"></span> ').find('.wpresmenu_icon_par');

        sub_ul.hide();
    });

    //adjust the a width on parent uls so it fits nicely with th eicon elemnt
    function adjust_expandable_items() {
        $('.wpresmenu_parent_item_li').each(function() {
            var t = $(this),
                main_ul_width = 0,
                icon = t.find('.wpresmenu_icon_par').first(),
                link = t.find('a.wpresmenu_parent_item').first();

            if(menu.hasClass('top')) {
                main_ul_width = window.innerWidth;
            } else {
                main_ul_width = menu_ul.innerWidth();
            }

            if(t.find('.wpresmenu_clear').length == 0) link.after('<br class="wpresmenu_clear"/>');
        });
    }
    adjust_expandable_items();

    //expand / collapse action (SUBLEVELS)
    $('.wpresmenu_icon_par').on('click',function() {
        var t = $(this),
            //child_ul = t.next('a').next('ul');
            child_ul = t.parent('li').find('ul.sub-menu').first();
        child_ul.slideToggle(300);
        t.toggleClass('wpresmenu_par_opened');
        t.parent('li').first().toggleClass('wpresmenu_no_border_bottom');
    });

    //helper - close all submenus when menu is hiding
    function close_sub_uls() {
        menu.find('ul.sub-menu').each(function() {
            var ul = $(this),
                icon = ul.parent('li').find('.wpresmenu_icon_par'),
                li = ul.parent('li');

            if(ul.is(':visible')) ul.slideUp(300);
            icon.removeClass('wpresmenu_par_opened');
            li.removeClass('wpresmenu_no_border_bottom');
        });
    }

    //fix the scaling issue by adding/replacing viewport metatag
    var mt = $('meta[name=viewport]');
    mt = mt.length ? mt : $('<meta name="viewport" />').appendTo('head');
    if(wpresmenu.zooming == 'no') {
        mt.attr('content', 'user-scalable=no, width=device-width, maximum-scale=1, minimum-scale=1');
    } else {
        mt.attr('content', 'user-scalable=yes, width=device-width, initial-scale=1.0, minimum-scale=1');
    }

    //Additional fixes on change device orientation
    if( $.browser.mozilla ) {
        screen.addEventListener("orientationchange", function() {updateOrientation()}); //firefox
    } else if( window.addEventListener ) {
        window.addEventListener('orientationchange', updateOrientation, false);
    }
    else {
        window.attachEvent( "orientationchange" );
    }
    function updateOrientation() {
        window.scrollBy(1,1);
        window.scrollBy(-1,-1);

        menu_width = menu.width();

        //update the page posion for left menu
        if(menu.is(':visible') && menu.hasClass('left')) {
            body.css({'left':menu_width});
            body.scrollLeft(0);
        }
    }




    //apply the SIDR for the left/right menu
    if(menu.hasClass('left') || menu.hasClass('right')) {

        //appy sidr
        var hor_pos = (menu.hasClass('left'))? 'left' : 'right';
        bar.sidr({
            name:'wpresmenu_menu',
            side: hor_pos,
            speed: animation_speed,
            onOpen: function(){ bar.addClass('menu_is_opened'); },
            onClose: function(){ bar.removeClass('menu_is_opened'); close_sub_uls();  }
        });

        //when link is clicked - hide the menu first and then change location to new page
        menu_a.on('click', function(e) {
            $.sidr('close', 'wpresmenu_menu');
        });

        if( wpresmenu.swipe != 'no' ) {
            $('body').touchwipe({
                wipeLeft: function() {
                  // Close
                  $.sidr('close', 'wpresmenu_menu');
                },
                wipeRight: function() {
                  // Open
                  $.sidr('open', 'wpresmenu_menu');
                },
                min_move_x: 60,
                min_move_y: 60,
                preventDefaultEvents: false
            });
        }

        $(window).resize(function(){
            target_width = (window.innerWidth < body.width())? body.width() : window.innerWidth;
            if(target_width > from_width && menu.is(':visible')) {
                $.sidr('close', 'wpresmenu_menu');
            }
        });


    } else if(menu.hasClass('top')) { //The top positioned menu

        body.prepend(menu);

        //show / hide the menu
        bar.on('click', function(e) {

            //scroll window top
            $("html, body").animate({ scrollTop: 0 }, animation_speed);

            close_sub_uls();
            menu.stop(true, false).slideToggle(animation_speed);


        });


        //when link is clicked - hide the menu first and then change location to new page
        menu_a.on('click', function(e) {
            e.preventDefault();
            var url = $(this).attr('href');

            menu.slideUp(animation_speed,function() {
                //go to the url from the link
                window.location.href = url;
            });
        });


        $(window).resize(function(){
            target_width = (window.innerWidth < body.width())? body.width() : window.innerWidth;
            if(target_width > from_width && menu.is(':visible')) {
                close_sub_uls();
                menu.slideUp(animation_speed, function() {});
            }
        });


    } //end if class left / top /right

} //end if menu enabled

答案 34 :(得分:0)

为什么我们不使用像

这样的东西
pgrep -f $cmd || $cmd

答案 35 :(得分:0)

这是一个更优雅,更安全,快速的&amp;脏方法,结合上面提供的答案。

用法

  1. 包含 sh_lock_functions.sh
  2. 使用 sh_lock_init
  3. 初始化
  4. 使用 sh_acquire_lock
  5. 锁定
  6. 使用 sh_check_lock
  7. 检查锁定
  8. 使用 sh_remove_lock
  9. 解锁

    脚本文件

    <强> sh_lock_functions.sh

    #!/bin/bash
    
    function sh_lock_init {
        sh_lock_scriptName=$(basename $0)
        sh_lock_dir="/tmp/${sh_lock_scriptName}.lock" #lock directory
        sh_lock_file="${sh_lock_dir}/lockPid.txt" #lock file
    }
    
    function sh_acquire_lock {
        if mkdir $sh_lock_dir 2>/dev/null; then #check for lock
            echo "$sh_lock_scriptName lock acquired successfully.">&2
            touch $sh_lock_file
            echo $$ > $sh_lock_file # set current pid in lockFile
            return 0
        else
            touch $sh_lock_file
            read sh_lock_lastPID < $sh_lock_file
            if [ ! -z "$sh_lock_lastPID" -a -d /proc/$sh_lock_lastPID ]; then # if lastPID is not null and a process with that pid exists
                echo "$sh_lock_scriptName is already running.">&2
                return 1
            else
                echo "$sh_lock_scriptName stopped during execution, reacquiring lock.">&2
                echo $$ > $sh_lock_file # set current pid in lockFile
                return 2
            fi
        fi
        return 0
    }
    
    function sh_check_lock {
        [[ ! -f $sh_lock_file ]] && echo "$sh_lock_scriptName lock file removed.">&2 && return 1
        read sh_lock_lastPID < $sh_lock_file
        [[ $sh_lock_lastPID -ne $$ ]] && echo "$sh_lock_scriptName lock file pid has changed.">&2  && return 2
        echo "$sh_lock_scriptName lock still in place.">&2
        return 0
    }
    
    function sh_remove_lock {
        rm -r $sh_lock_dir
    }
    

    用法示例

    <强> sh_lock_usage_example.sh

    #!/bin/bash
    . /path/to/sh_lock_functions.sh # load sh lock functions
    
    sh_lock_init || exit $?
    
    sh_acquire_lock
    lockStatus=$?
    [[ $lockStatus -eq 1 ]] && exit $lockStatus
    [[ $lockStatus -eq 2 ]] && echo "lock is set, do some resume from crash procedures";
    
    #monitoring example
    cnt=0
    while sh_check_lock # loop while lock is in place
    do
        echo "$sh_scriptName running (pid $$)"
        sleep 1
        let cnt++
        [[ $cnt -gt 5 ]] && break
    done
    
    #remove lock when process finished
    sh_remove_lock || exit $?
    
    exit 0
    

    功能

    • 使用文件,目录和进程ID的组合进行锁定以确保进程尚未运行
    • 您可以检测脚本在锁定删除之前是否停止(例如,进程终止,关闭,错误等)
    • 您可以检查锁定文件,并在缺少锁定时使用它来触发进程关闭
    • 详细,输出错误消息以便于调试

答案 36 :(得分:0)

又快又脏?

#!/bin/sh

if [ -f sometempfile ]
  echo "Already running... will now terminate."
  exit
else
  touch sometempfile
fi

..do what you want here..

rm sometempfile

答案 37 :(得分:0)

鸡群的路径是要走的路。想想当脚本突然死亡时会发生什么。在羊群中你只是松散了羊群,但这不是问题。另外,请注意,一个邪恶的伎俩就是对剧本本身进行一系列攻击..但这当然会让你全速前进到权限问题中。

答案 38 :(得分:-1)

如果您的脚本名称是唯一的,那么这将有效:

#!/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

答案 39 :(得分:-2)

尝试类似下面的内容,

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

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