bash 确保一次只运行一个 shell 脚本实例的快速而肮脏的方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/185451/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Quick-and-dirty way to ensure only one instance of a shell script is running at a time
提问by raldi
What's a quick-and-dirty way to make sure that only one instance of a shell script is running at a given time?
确保在给定时间只运行一个 shell 脚本实例的快速而简单的方法是什么?
采纳答案by bmdhacks
Here's an implementation that uses a lockfileand echoes a PID into it. This serves as a protection if the process is killed before removing the pidfile:
这是一个使用锁文件并将 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}
The trick here is the kill -0
which doesn't deliver any signal but just checks if a process with the given PID exists. Also the call to trap
will ensure that the lockfileis removed even when your process is killed (except kill -9
).
这里的技巧是kill -0
它不传递任何信号,而只是检查具有给定 PID 的进程是否存在。此外,即使您的进程被终止,调用 也trap
将确保删除锁文件(除了kill -9
)。
回答by Alex B
Use flock(1)
to make an exclusive scoped lock a on file descriptor. This way you can even synchronize different parts of the script.
用于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
This ensures that code between (
and )
is run only by one process at a time and that the process doesn't wait too long for a lock.
这确保了(
和之间的代码)
一次仅由一个进程运行,并且该进程不会为锁定等待太长时间。
Caveat: this particular command is a part of util-linux
. If you run an operating system other than Linux, it may or may not be available.
警告:这个特定的命令是util-linux
. 如果您运行 Linux 以外的操作系统,则它可能可用也可能不可用。
回答by lhunath
All approaches that test the existence of "lock files" are flawed.
所有测试“锁定文件”是否存在的方法都有缺陷。
Why? Because there is no way to check whether a file exists and create it in a single atomic action. Because of this; there is a race condition that WILLmake your attempts at mutual exclusion break.
为什么?因为没有办法在单个原子操作中检查文件是否存在并创建它。因为这; 有一个竞争条件是WILL在互斥休息让你尝试。
Instead, you need to use mkdir
. mkdir
creates a directory if it doesn't exist yet, and if it does, it sets an exit code. More importantly, it does all this in a single atomic action making it perfect for this scenario.
相反,您需要使用mkdir
. mkdir
如果目录尚不存在,则创建一个目录,如果存在,则设置退出代码。更重要的是,它在单个原子操作中完成所有这些,使其非常适合这种情况。
if ! mkdir /tmp/myscript.lock 2>/dev/null; then
echo "Myscript is already running." >&2
exit 1
fi
For all details, see the excellent BashFAQ: http://mywiki.wooledge.org/BashFAQ/045
有关所有详细信息,请参阅优秀的 BashFAQ: http://mywiki.wooledge.org/BashFAQ/045
If you want to take care of stale locks, fuser(1)comes in handy. The only downside here is that the operation takes about a second, so it isn't instant.
如果您想处理过时的锁,fuser(1)会派上用场。这里唯一的缺点是操作需要大约一秒钟,所以它不是即时的。
Here's a function I wrote once that solves the problem using fuser:
这是我曾经写过的一个函数,它使用 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= 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
}
You can use it in a script like so:
您可以在脚本中使用它,如下所示:
mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }
If you don't care about portability (these solutions should work on pretty much any UNIX box), Linux' fuser(1)offers some additional options and there is also flock(1).
如果你不关心可移植性(这些解决方案几乎可以在任何 UNIX 机器上运行),Linux 的fuser(1)提供了一些额外的选项,还有flock(1)。
回答by Cowan
There's a wrapper around the flock(2) system call called, unimaginatively, flock(1). This makes it relatively easy to reliably obtain exclusive locks without worrying about cleanup etc. There are examples on the man pageas to how to use it in a shell script.
flock(2) 系统调用周围有一个包装器,毫无想象力地称为 flock(1)。这使得可靠地获取排他锁相对容易,而无需担心清理等。手册页上有关于如何在 shell 脚本中使用它的示例。
回答by Gunstick
You need an atomic operation, like flock, else this will eventually fail.
你需要一个原子操作,比如 flock,否则这最终会失败。
But what to do if flock is not available. Well there is mkdir. That's an atomic operation too. Only one process will result in a successful mkdir, all others will fail.
但是如果 flock 不可用怎么办。嗯,有 mkdir。这也是一个原子操作。只有一个进程会导致 mkdir 成功,所有其他进程都将失败。
So the code is:
所以代码是:
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
You need to take care of stale locks else aftr a crash your script will never run again.
您需要处理过时的锁,否则崩溃后您的脚本将永远不会再次运行。
回答by Gunstick
To make locking reliable you need an atomic operation. Many of the above proposals are not atomic. The proposed lockfile(1) utility looks promising as the man-page mentioned, that its "NFS-resistant". If your OS does not support lockfile(1) and your solution has to work on NFS, you have not many options....
为了使锁定可靠,您需要一个原子操作。上述许多提议都不是原子的。正如手册页提到的那样,提议的 lockfile(1) 实用程序看起来很有前途,它的“抗 NFS”。如果您的操作系统不支持 lockfile(1) 并且您的解决方案必须在 NFS 上运行,那么您没有太多选择....
NFSv2 has two atomic operations:
NFSv2 有两个原子操作:
- symlink
- rename
- 符号链接
- 改名
With NFSv3 the create call is also atomic.
使用 NFSv3,创建调用也是原子的。
Directory operations are NOT atomic under NFSv2 and NFSv3 (please refer to the book 'NFS Illustrated' by Brent Callaghan, ISBN 0-201-32570-5; Brent is a NFS-veteran at Sun).
NFSv2 和 NFSv3 下的目录操作不是原子操作(请参阅 Brent Callaghan 所著的“NFS Illustrated”一书,ISBN 0-201-32570-5;Brent 是 Sun 的 NFS 资深人士)。
Knowing this, you can implement spin-locks for files and directories (in shell, not PHP):
知道了这一点,您可以为文件和目录实现自旋锁(在 shell 中,而不是在 PHP 中):
lock current dir:
锁定当前目录:
while ! ln -s . lock; do :; done
lock a file:
锁定文件:
while ! ln -s ${f} ${f}.lock; do :; done
unlock current dir (assumption, the running process really acquired the lock):
解锁当前目录(假设,正在运行的进程确实获得了锁):
mv lock deleteme && rm deleteme
unlock a file (assumption, the running process really acquired the lock):
解锁文件(假设,正在运行的进程确实获得了锁):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Remove is also not atomic, therefore first the rename (which is atomic) and then the remove.
删除也不是原子的,因此首先重命名(这是原子的),然后是删除。
For the symlink and rename calls, both filenames have to reside on the same filesystem. My proposal: use only simple filenames (no paths) and put file and lock into the same directory.
对于符号链接和重命名调用,两个文件名必须位于同一个文件系统中。我的建议:只使用简单的文件名(无路径)并将文件和锁放在同一目录中。
回答by Mikel
Another option is to use shell's noclobber
option by running set -C
. Then >
will fail if the file already exists.
另一种选择是noclobber
通过运行来使用 shell 的选项set -C
。然后,>
如果该文件已经存在,就会失败。
In brief:
简单来说:
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
This causes the shell to call:
这会导致外壳调用:
open(pathname, O_CREAT|O_EXCL)
which atomically creates the file or fails if the file already exists.
它以原子方式创建文件,或者如果文件已经存在则失败。
According to a comment on BashFAQ 045, this may fail in ksh88
, but it works in all my shells:
根据对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
Interesting that pdksh
adds the O_TRUNC
flag, but obviously it's redundant:
either you're creating an empty file, or you're not doing anything.
pdksh
添加O_TRUNC
标志很有趣,但显然它是多余的:
要么您正在创建一个空文件,要么您什么都不做。
How you do the rm
depends on how you want unclean exits to be handled.
您如何执行rm
取决于您希望如何处理不干净的出口。
Delete on clean exit
在干净退出时删除
New runs fail until the issue that caused the unclean exit to be resolved and the lockfile is manually removed.
新运行失败,直到解决导致不干净退出的问题并手动删除锁文件。
# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"
Delete on any exit
在任何出口删除
New runs succeed provided the script is not already running.
如果脚本尚未运行,则新运行成功。
trap 'rm "$lockfile"' EXIT
回答by Mark Setchell
You can use GNU Parallel
for this as it works as a mutex when called as sem
. So, in concrete terms, you can use:
您可以使用GNU Parallel
它,因为它在调用 as 时用作互斥锁sem
。因此,具体而言,您可以使用:
sem --id SCRIPTSINGLETON yourScript
If you want a timeout too, use:
如果您也想要超时,请使用:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
Timeout of <0 means exit without running script if semaphore is not released within the timeout, timeout of >0 mean run the script anyway.
超时 <0 表示如果在超时内没有释放信号量,则退出而不运行脚本,超时 >0 表示无论如何都要运行脚本。
Note that you should give it a name (with --id
) else it defaults to the controlling terminal.
请注意,您应该为其命名(带有--id
),否则它默认为控制终端。
GNU Parallel
is a very simple install on most Linux/OSX/Unix platforms - it is just a Perl script.
GNU Parallel
在大多数 Linux/OSX/Unix 平台上是一个非常简单的安装——它只是一个 Perl 脚本。
回答by Mark Stinson
For shell scripts, I tend to go with the mkdir
over flock
as it makes the locks more portable.
对于 shell 脚本,我倾向于使用mkdir
over,flock
因为它使锁更易于移植。
Either way, using set -e
isn't enough. That only exits the script if any command fails. Your locks will still be left behind.
无论哪种方式,使用set -e
是不够的。只有在任何命令失败时才会退出脚本。你的锁仍然会被留下。
For proper lock cleanup, you really should set your traps to something like this psuedo code (lifted, simplified and untested but from actively used scripts) :
为了正确清理锁,你真的应该将你的陷阱设置为这样的伪代码(提升、简化和未经测试,但来自积极使用的脚本):
#=======================================================================
# 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
Here's what will happen. All traps will produce an exit so the function __sig_exit
will always happen (barring a SIGKILL) which cleans up your locks.
这就是将会发生的事情。所有陷阱都会产生一个退出,因此该函数__sig_exit
将始终发生(除非 SIGKILL),它会清除您的锁。
Note: my exit values are not low values. Why? Various batch processing systems make or have expectations of the numbers 0 through 31. Setting them to something else, I can have my scripts and batch streams react accordingly to the previous batch job or script.
注意:我的退出值不低。为什么?各种批处理系统生成或期望数字 0 到 31。将它们设置为其他值,我可以让我的脚本和批处理流对之前的批处理作业或脚本做出相应的反应。
回答by Majal
Reallyquick and reallydirty? This one-liner on the top of your script will work:
真正快速,真的脏吗?脚本顶部的这一行将起作用:
[[ $(pgrep -c "`basename \"##代码##\"`") -gt 1 ]] && exit
Of course, just make sure that your script name is unique. :)
当然,只需确保您的脚本名称是唯一的。:)