有什么快速而简单的方法可以确保在给定时间内只有一个shell脚本实例在运行?
当前回答
现有的答案要么依赖于CLI实用程序群,要么没有正确地保护锁文件。flock实用程序在所有非linux系统(即FreeBSD)上都不可用,在NFS上也不能正常工作。
在我从事系统管理和系统开发的早期,有人告诉我,一种安全且相对可移植的创建锁文件的方法是使用mkemp(3)或mkemp(1)创建临时文件,将标识信息写入临时文件(即PID),然后将临时文件硬链接到锁文件。如果链接成功,那么您已经成功地获得了锁。
当在shell脚本中使用锁时,我通常会在共享概要文件中放置一个obtain_lock()函数,然后从脚本中获取它。下面是一个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;
};
lock功能的使用示例如下:
#!/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环境中都使用了上述方法。
其他回答
为什么我们不用像这样的东西
pgrep -f $cmd || $cmd
如果您不想或不能使用flock(例如,您没有使用共享文件系统),请考虑使用外部服务,如lockable。
它暴露了咨询锁原语,就像flock一样。特别地,你可以通过以下方式获取锁:
https://lockable.dev/api/acquire/my-lock-name
然后通过
https://lockable.dev/api/release/my-lock-name
通过将脚本执行与锁获取和释放结合在一起,您可以确保在任何给定时间只有一个流程实例在运行。
一些unix具有与前面提到的flock非常相似的lockfile。
从手册中:
Lockfile可以用来创建一个 或者更多的信号量文件。如果锁, 文件不能创建所有指定的 文件(在指定的顺序),它 等待睡眠时间(默认为8) 秒并重试最后一个文件 没有成功。您可以指定 直到重试的次数 返回失败。如果数字 重试次数为-1(默认值,即 -r-1)锁文件将永远重试。
测试“锁文件”是否存在的简单方法是有缺陷的。
为什么?因为它们不会检查文件是否存在,也不会在单个原子操作中创建文件。因为这个;有一个竞争条件会使你的互斥失败。
相反,您可以使用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)会派上用场。唯一的缺点是这个操作大约需要一秒钟,所以它不是即时的。
下面是我曾经写过的一个函数,它使用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的fuser(1)提供了一些额外的选项,还有flock(1)。
这个我在任何地方都没有发现,它使用read,我不确切地知道read是否实际上是原子的,但到目前为止它对我来说很有用……它很有趣,因为它只是bash内置的,这是一个进程内实现,你启动locker协进程并使用它的I / O来管理锁,同样可以在进程间完成,只需将目标I / O从locker文件描述符交换到文件系统上的文件描述符(exec 3<>/file && exec 4</file)
## gives locks
locker() {
locked=false
while read l; do
case "$l" in
lock)
if $locked; then
echo false
else
locked=true
echo true
fi
;;
unlock)
if $locked; then
locked=false
echo true
else
echo false
fi
;;
*)
echo false
;;
esac
done
}
## locks
lock() {
local response
echo lock >&${locker[1]}
read -ru ${locker[0]} response
$response && return 0 || return 1
}
## unlocks
unlock() {
local response
echo unlock >&${locker[1]}
read -ru ${locker[0]} response
$response && return 0 || return 1
}