快速和肮脏的方式来确保一次只运行一个shell脚本的一个实例
什么是快速和肮脏的方法来确保在给定的时间只有一个shell脚本的实例正在运行?
这是一个使用锁定文件的实现,并在其中添加一个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
)。
使用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以外的操作系统,则可能可用,也可能不可用。
所有测试“锁定文件”存在的方法都是有缺陷的。
为什么? 因为没有办法检查文件是否存在并在单个原子动作中创建它。 因为这个; 有一个竞争条件,将使你的尝试互相排斥。
相反,你需要使用mkdir
。 如果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
如果你想照顾陈旧的锁, 热熔器(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的定影器(1)提供了一些额外的选项,也有群(1) 。
羊群(2)的系统调用叫做无用想象的羊群(1)。 这使得可靠地获得独占锁而不必担心清理等情况变得相对容易。 在手册页上有关于如何在shell脚本中使用它的示例。
你需要像羊群一样的原子操作,否则这将最终失败。
但如果鸡群不可用,该怎么办。 那么有mkdir。 这也是一个原子操作。 只有一个进程会导致成功的mkdir,所有其他进程将失败。
所以代码是:
if mkdir /var/lock/.myscript.exclusivelock then # do stuff : rmdir /var/lock/.myscript.exclusivelock fi
你需要照顾陈旧的锁,否则在脚本崩溃时你的脚本将永远不会再运行。
另一种选择是通过运行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
标志,但显然这是多余的:
要么你正在创建一个空文件,或者你没有做任何事情。
你怎么做取决于你如何处理不洁净的出口。
在干净的出口删除
新的运行失败,直到解决不清洁出口的问题,并手动删除锁定文件。
# acquire lock # do work (code here may call exit, etc.) rm "$lockfile"
删除任何退出
如果脚本尚未运行,则新的运行成功。
trap 'rm "$lockfile"' EXIT
为了使锁定可靠,你需要一个原子操作。 上述提案中的很多都不是原子的。 所提出的lockfile(1)实用程序看起来很有希望,正如所提到的手册页所述,即“NFS-resistant”。 如果您的操作系统不支持锁定文件(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
删除也不是原子的,因此首先重命名(这是原子),然后删除。
对于符号链接和重命名调用,这两个文件名必须驻留在同一个文件系统上。 我的建议:只使用简单的文件名(无路径),并将文件和锁放入同一目录。
对于shell脚本,我倾向于使用mkdir
,因为它使得这些锁更加便于携带。
无论如何,使用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都有所期待。将其设置为别的,我可以让我的脚本和批处理流对之前的批处理作业或脚本作出相应的反应。
真的很快, 真的很脏? 脚本顶部的这一行代码将起作用:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
当然,只要确保您的脚本名称是唯一的。 🙂
你可以使用GNU Parallel
,因为它在作为sem
调用时作为一个互斥体。 所以,具体而言,您可以使用:
sem --id SCRIPTSINGLETON yourScript
如果您也想要超时,请使用:
sem --id SCRIPTSINGLETON --semaphoretimeout 10 yourScript
请注意,您应该给它一个名称(带--id
),否则它默认为控制终端。
GNU Parallel
在大多数Linux / OSX / Unix平台上是一个非常简单的安装 – 它只是一个Perl脚本。
在已知的位置创建一个锁定文件并检查脚本启动时是否存在? 如果有人试图追踪阻止脚本执行的错误实例,将PID放在文件中可能会有所帮助。
这是一种将原子目录锁定与通过PID检查陈旧锁定的方法相结合,如果失效则重新启动。 另外,这不依赖任何bashisms。
#!/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
这个例子在人群中进行了解释,但是需要一些干预,因为我们应该管理错误和退出代码:
#!/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 -v grep来删除寄生虫nad。 并与数字进行比较。 它的复杂和不确定性
在定位Debian机器时,我发现lockfile-progs
包是一个很好的解决方案。 procmail
还附带了一个lockfile
工具。 不过有时候我还没有被这些东西卡住。
这是我的解决方案,它使用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 }
一些unix的lockfile与已经提到的flock
非常相似。
从手册:
lockfile可以用来创建一个或多个信号量文件。 如果锁定文件无法创建所有指定文件(按照指定的顺序),它将等待休眠时间(默认为8)秒,然后重试最后一个不成功的文件。 您可以指定要执行的重试次数,直到返回故障。 如果重试次数是-1(默认值,即-r-1),则lockfile将永久重试。
如果在这个线程的其他地方已经描述过的群体的限制对你来说不是问题,那么这个应该是有效的:
#!/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
PID和lockfiles绝对是最可靠的。 当你试图运行该程序时,它可以检查lockfile是否存在,它可以使用ps
来查看进程是否仍在运行。 如果不是,脚本可以启动,将lockfile中的PID更新为自己的。
实际上,虽然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
选项将确保重定向命令将失败,如果文件已经存在。 所以重定向命令实际上是原子的 – 你用一个命令编写并检查文件。 您不需要在文件末尾删除锁定文件 – 它将被陷阱删除。 我希望这有助于稍后阅读的人。
PS我没有看到Mikel已经正确地回答了这个问题,虽然他没有包含陷阱命令来减少在用Ctrl-C停止脚本之后锁定文件将被遗留的可能性。 所以这是完整的解决方案
我使用一个简单的方法来处理陈旧的锁定文件。
请注意,上述一些存储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
我发现bmdhack的解决方案是最实用的,至少在我的使用情况。 使用flock和lockfile依赖于在脚本终止时使用rm来移除锁定文件,这并不总能得到保证(例如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}
当然,这个脚本仍然存在这样的缺陷,即可能同时启动的进程具有竞赛危险,因为锁定测试和集合操作不是单个原子动作。 But the proposed solution for this by lhunath to use mkdir has the flaw that a killed script may leave behind the directory, thus preventing other instances from running.
Simply add [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
at the beginning of your script. It's a boilerplate code from man flock. To realize how it works i wrote a script and run it simultaneously from two consoles:
#!/bin/bash if [ "${FLOCKER}" != "$0" ]; then echo "FLOCKER=$FLOCKER \$0=$0 ($$)" exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || : else echo "FLOCKER equals \$0 = $FLOCKER ($$)" fi sleep 10 echo "Process $$ finished"
I have not fully realized how it works, but it seems it runs itself again using itself as a lockfile. FLOCKER
set to "$0"
just to set some notnull reasonable value. || :
to do nothing if something went wrong.
It seems to not work on Debian 7, but seems to work back again with experimental util-linux 2.25 package. It writes "flock: … Text file busy". It could be overridden by disabling write permission on your script.
I wanted to do away with lockfiles, lockdirs, special locking programs and even pidof
since it isn't found on all Linux installations. Also wanted to have the simplest code possible (or at least as few lines as possible). Simplest if
statement, in one line:
if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
The existing answers posted either rely on the CLI utility flock
or do not properly secure the lock file. The flock utility is not available on all non-Linux systems (ie FreeBSD), and does not work properly on NFS.
In my early days of system administration and system development, I was told that a safe and relatively portable method of creating a lock file was to create a temp file using mkemp(3)
or mkemp(1)
, write identifying information to the temp file (ie PID), then hard link the temp file to the lock file. If the link was successful, then you have successfully obtained the lock.
When using locks in shell scripts, I typically place an obtain_lock()
function in a shared profile and then source it from the scripts. Below is an example of my lock function:
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; };
The following is an example of how to use the lock function:
#!/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
Remember to call clean_up
at any exit points in your script.
I've used the above in both Linux and FreeBSD environments.
The flock path is the way to go. Think about what happens when the script suddenly dies. In the flock-case you just loose the flock, but that is not a problem. Also, note that an evil trick is to take a flock on the script itself .. but that of course lets you run full-steam-ahead into permission problems.
Take a look to FLOM (Free LOck Manager) http://sourceforge.net/projects/flom/ : you can synchronize commands and/or scripts using abstract resources that does not need lock files in a filesystem. You can synchronize commands running in different systems without a NAS (Network Attached Storage) like an NFS (Network File System) server.
Using the simplest use case, serializing "command1" and "command2" may be as easy as executing:
flom -- command1
和
flom -- command2
from two different shell scripts.
Here is a more elegant, fail-safe, quick & dirty method, combining the answers provided above.
用法
- include sh_lock_functions.sh
- init using sh_lock_init
- lock using sh_acquire_lock
- check lock using sh_check_lock
- unlock using sh_remove_lock
Script File
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
特征
- Uses a combination of file, directory and process id to lock to make sure that the process is not already running
- You can detect if the script stopped before lock removal (eg. process kill, shutdown, error etc.)
- You can check the lock file, and use it to trigger a process shutdown when the lock is missing
- Verbose, outputs error messages for easier debug
The semaphoric utility uses flock
(as discussed above, eg by presto8) to implement a counting semaphore . It enables any specific number of concurrent processes you want. We use it to limit the level of concurrency of various queue worker processes.
It's like sem but much lighter-weight. (Full disclosure: I wrote it after finding the sem was way too heavy for our needs and there wasn't a simple counting semaphore utility available.)
why dont we use something like
pgrep -f $cmd || $cmd
if [ 1 -ne $(/bin/fuser "$0" 2>/dev/null | wc -w) ]; then exit 1 fi
I have a simple solution based on the file name
#!/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.