【发布时间】:2010-09-16 04:10:08
【问题描述】:
确保在给定时间只有一个 shell 脚本实例在运行的快速而简单的方法是什么?
【问题讨论】:
-
相关@Unix.SE:unix.stackexchange.com/questions/22044/…
标签: bash shell process lockfile
确保在给定时间只有一个 shell 脚本实例在运行的快速而简单的方法是什么?
【问题讨论】:
标签: bash shell process lockfile
看看 FLOM(免费锁管理器)http://sourceforge.net/projects/flom/:您可以使用不需要文件系统中的锁文件的抽象资源来同步命令和/或脚本。您可以同步在不同系统中运行的命令,而无需像 NFS(网络文件系统)服务器这样的 NAS(网络附加存储)。
使用最简单的用例,序列化“command1”和“command2”可能就像执行一样简单:
flom -- command1
和
flom -- command2
来自两个不同的 shell 脚本。
【讨论】:
flom 的几率是多少?
结合上面提供的答案,这是一个更优雅、安全、快速的 &dirty 方法。
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
【讨论】:
为什么我们不使用类似的东西
pgrep -f $cmd || $cmd
【讨论】:
$cmd 的两个实例。
if [ 1 -ne $(/bin/fuser "$0" 2>/dev/null | wc -w) ]; then
exit 1
fi
【讨论】:
我有一个基于文件名的简单解决方案
#!/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.
【讨论】:
晚会,使用@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
【讨论】:
-lt 3?如果已经有一个实例正在运行,它不会开始吗?还是 emaxclient 总是启动 2 个实例?
这一行答案来自相关人员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.
【讨论】:
我在任何地方都没有提到这个,它使用 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
}
【讨论】:
我对现有答案有以下问题:
$0 或$BASH_SOURCE 进行锁定,通常参考man flock 中的示例。
当由于更新或编辑导致下一次运行打开并获得对新脚本文件的锁定而替换脚本时,即使另一个持有已删除文件锁定的实例仍在运行,此操作也会失败。这个答案可以:
flock,因为它让内核提供锁定...前提是锁定文件是原子创建的而不是替换的。flock 指示,而不是通过锁定文件存在。它不是单行器,但没有 cmets 也没有错误消息,它足够小:
#!/bin/bash
LOCKFILE=/var/lock/TODO
set -o noclobber
exec {lockfd}<> "${LOCKFILE}" || exit 1
set +o noclobber # depends on what you need
flock --exclusive --nonblock ${lockfd} || exit 1
但我更喜欢 cmets 和错误消息:
#!/bin/bash
# TODO Set a lock file name
LOCKFILE=/var/lock/myprogram.lock
# Set noclobber option to ensure lock file is not REPLACED.
set -o noclobber
# Open lock file for R+W on a new file descriptor
# and assign the new file descriptor to "lockfd" variable.
# This does NOT obtain a lock but ensures the file exists and opens it.
exec {lockfd}<> "${LOCKFILE}" || {
echo "pid=$$ failed to open LOCKFILE='${LOCKFILE}'" 1>&2
exit 1
}
# TODO!!!! undo/set the desired noclobber value for the remainder of the script
set +o noclobber
# Lock on the allocated file descriptor or fail
# Adjust flock options e.g. --noblock as needed
flock --exclusive --nonblock ${lockfd} || {
echo "pid=$$ failed to obtain lock fd='${lockfd}' LOCKFILE='${LOCKFILE}'" 1>&2
exit 1
}
# DO work here
echo "pid=$$ obtained exclusive lock fd='${lockfd}' LOCKFILE='${LOCKFILE}'"
# Can unlock after critical section and do more work after unlocking
#flock -u ${lockfd};
# if unlocking then might as well close lockfd too
#exec {lockfd}<&-
【讨论】:
kill $(cat lockfile) 的习惯并杀死不相关的进程,这是依赖锁时会发生的问题文件存在并且必须清理过时的锁定文件。无需清洁 - 没问题。
如果您的脚本名称是唯一的,这将起作用:
#!/bin/bash
if [ $(pgrep -c $(basename $0)) -gt 1 ]; then
echo $(basename $0) is already running
exit 0
fi
如果脚本名不是唯一的,这适用于大多数 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
【讨论】:
试试下面的方法,
ab=`ps -ef | grep -v grep | grep -wc processname`
然后使用 if 循环将变量与 1 匹配。
【讨论】:
ps -ef | egrep -v "(grep|$$)" | grep -wc processname 这样的东西,如果检查的目的是禁止当前脚本的多个实例,它将与当前进程不匹配。