Ale*_*x B 204
用于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
Run Code Online (Sandbox Code Playgroud)
这将确保之间的代码(,并)通过一个进程时间和进程不等待锁太长只运行.
警告:这个特殊命令是其中的一部分util-linux.如果您运行Linux以外的操作系统,它可能可用,也可能不可用.
lhu*_*ath 155
所有测试"锁定文件"存在的方法都存在缺陷.
为什么?因为无法检查文件是否存在并在单个原子操作中创建它.因为这; 有一个竞争条件是WILL在互斥休息让你尝试.
相反,你需要使用mkdir. mkdir如果目录尚不存在,则创建一个目录,如果存在,则设置退出代码.更重要的是,它在一个原子动作中完成所有这一切,使其成为这种情况的完美之选.
if ! mkdir /tmp/myscript.lock 2>/dev/null; then
echo "Myscript is already running." >&2
exit 1
fi
Run Code Online (Sandbox Code Playgroud)
有关所有细节,请参阅优秀的BashFAQ: http://mywiki.wooledge.org/BashFAQ/045
如果你想要处理过时的锁,热熔器(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
}
Run Code Online (Sandbox Code Playgroud)
您可以在脚本中使用它,如下所示:
mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }
Run Code Online (Sandbox Code Playgroud)
如果您不关心可移植性(这些解决方案几乎可以在任何UNIX机器上运行),Linux的fuser(1)提供了一些额外的选项,还有flock(1).
bmd*_*cks 102
这是一个使用锁文件并将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}
Run Code Online (Sandbox Code Playgroud)
这里的技巧是kill -0不提供任何信号,只是检查是否存在具有给定PID的进程.此外,调用trap将确保即使您的进程被终止也会删除锁定文件(除外kill -9).
小智 27
你需要一个原子操作,比如flock,否则这最终会失败.
但是如果没有flock怎么办.那么有mkdir.这也是一个原子操作.只有一个进程会导致成功的mkdir,所有其他进程都将失败.
所以代码是:
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
Run Code Online (Sandbox Code Playgroud)
你需要处理陈旧的锁定,否则你的脚本永远不会再运行崩溃.
小智 24
要使锁定可靠,您需要进行原子操作.上述许多提案都不是原子的.建议的lockfile(1)实用程序看起来很有前途,因为它提到了"NFS-resistant".如果您的操作系统不支持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
Run Code Online (Sandbox Code Playgroud)
锁定文件:
while ! ln -s ${f} ${f}.lock; do :; done
Run Code Online (Sandbox Code Playgroud)
解锁当前dir(假设,运行进程确实获得了锁定):
mv lock deleteme && rm deleteme
Run Code Online (Sandbox Code Playgroud)
解锁文件(假设,正在运行的进程确实获得了锁定):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Run Code Online (Sandbox Code Playgroud)
删除也不是原子的,因此首先重命名(这是原子的)然后删除.
对于符号链接和重命名调用,两个文件名必须驻留在同一文件系统上.我的建议:只使用简单的文件名(没有路径),并将文件和锁定放在同一目录中.
Mik*_*kel 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
Run Code Online (Sandbox Code Playgroud)
这导致shell调用:
open(pathname, O_CREAT|O_EXCL)
Run Code Online (Sandbox Code Playgroud)
以原子方式创建文件或如果文件已存在则失败.
根据对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
Run Code Online (Sandbox Code Playgroud)
有意pdksh添加O_TRUNC标志,但显然它是多余的:
要么你创建一个空文件,要么你没有做任何事情.
你如何做到这rm取决于你希望如何处理不洁净的出口.
在干净的出口处删除
新的运行失败,直到导致不正常退出的问题得到解决并且手动删除锁定文件.
# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"
Run Code Online (Sandbox Code Playgroud)
删除任何退出
如果脚本尚未运行,则新运行成功.
trap 'rm "$lockfile"' EXIT
Run Code Online (Sandbox Code Playgroud)
Mar*_*ell 18
您可以使用GNU Parallel它,因为它在调用时用作互斥锁sem.因此,具体而言,您可以使用:
sem --id SCRIPTSINGLETON yourScript
Run Code Online (Sandbox Code Playgroud)
如果您也想要超时,请使用:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
Run Code Online (Sandbox Code Playgroud)
超时<0表示在没有运行脚本的情况下退出,如果在超时内未释放信号量,则超时> 0表示无论如何都要运行脚本.
请注意,您应该为其命名(with --id),否则它默认为控制终端.
GNU Parallel 在大多数Linux/OSX/Unix平台上安装非常简单 - 它只是一个Perl脚本.
小智 16
对于shell脚本,我倾向于使用mkdirover,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
Run Code Online (Sandbox Code Playgroud)
这是将要发生的事情.所有陷阱都会产生一个退出,因此该函数__sig_exit将始终发生(除非是SIGKILL),它会清除你的锁.
注意:我的退出值不是低值.为什么?各种批处理系统对数字0到31产生或期望.将它们设置为其他东西,我可以让我的脚本和批处理流相应地响应先前的批处理作业或脚本.
Maj*_*jal 13
真的很快,真的很脏?脚本顶部的这个单行将起作用:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
Run Code Online (Sandbox Code Playgroud)
当然,只需确保您的脚本名称是唯一的.:)
如果集群的限制(已经在本线程的其他地方描述过)对您来说不是问题,那么这应该可行:
#!/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
Run Code Online (Sandbox Code Playgroud)
这个例子在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
Run Code Online (Sandbox Code Playgroud)
您可以使用其他方法,列出我过去使用的进程.但这种方法比上述方法更复杂.你应该按ps列出进程,按名称过滤,附加过滤器grep -v grep用于删除寄生虫nad最后用grep -c计算它.并与数字进行比较.它复杂而不确定
在脚本的开头添加此行
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
Run Code Online (Sandbox Code Playgroud)
这是来自 manflock 的样板代码。
如果您想要更多日志记录,请使用这个
[ "${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."
Run Code Online (Sandbox Code Playgroud)
这可以使用实用程序设置和检查锁flock。该代码通过检查 FLOCKER 变量来检测它是否是第一次运行,如果它没有设置为脚本名称,那么它会尝试使用flock并初始化FLOCKER变量再次递归地启动脚本,如果FLOCKER设置正确,则在上一次迭代中进行flock成功了,就可以继续了。如果锁正忙,则会失败并显示可配置的退出代码。
它似乎无法在 Debian 7 上运行,但似乎可以通过实验性 util-linux 2.25 软件包再次运行。它写“flock:...文本文件忙”。可以通过禁用脚本的写入权限来覆盖它。
这是一种将原子目录锁定与通过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
Run Code Online (Sandbox Code Playgroud)
发布的现有答案要么依赖于 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;
};
Run Code Online (Sandbox Code Playgroud)
以下是如何使用锁定功能的示例:
#!/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
Run Code Online (Sandbox Code Playgroud)
请记住clean_up在脚本中的任何退出点调用。
我已经在 Linux 和 FreeBSD 环境中使用了上述内容。