我有一个bash脚本我想每隔5分钟从cron运行一次......但是之前的脚本运行有可能还没有完成......在这种情况下,我希望新的运行只是退出。我不想只依赖/ tmp中的锁文件..我想确保在我尊重锁文件(或其他)之前该进程实际上正在运行...
这是我到目前为止从互联网上偷来的东西......我如何让它变得有点聪明?还是有一种完全不同的方式更好?
if [ -f /tmp/mylockFile ] ; then
echo 'Script is still running'
else
echo 1 > /tmp/mylockFile
/* Do some stuff */
rm -f /tmp/mylockFile
fi
答案 0 :(得分:20)
# Use a lockfile containing the pid of the running process
# If script crashes and leaves lockfile around, it will have a different pid so
# will not prevent script running again.
#
lf=/tmp/pidLockFile
# create empty lock file if none exists
cat /dev/null >> $lf
read lastPID < $lf
# if lastPID is not null and a process with that pid exists , exit
[ ! -z "$lastPID" -a -d /proc/$lastPID ] && exit
echo not running
# save my pid in the lock file
echo $$ > $lf
# sleep just to make testing easier
sleep 5
此脚本中至少有一个竞争条件。不要将它用于生命支持系统,哈哈。但它应该适用于您的示例,因为您的环境不会同时启动两个脚本。有很多方法可以使用更多的原子锁,但它们通常依赖于可选的安装特定的东西,或者在NFS等方面的工作方式不同......
答案 1 :(得分:9)
如果您有幸在您的发行版中获得它,可能需要查看flock
命令的手册页。
NAME flock - Manage locks from shell scripts SYNOPSIS flock [-sxon] [-w timeout] lockfile [-c] command...
答案 2 :(得分:7)
永远不要使用锁文件始终使用锁目录。 在您的特定情况下,它不是那么重要,因为脚本的开始是以5分钟的间隔安排的。但是,如果您将此代码重新用于Web服务器cgi脚本,那么您就是干杯。
if mkdir /tmp/my_lock_dir 2>/dev/null
then
echo "running now the script"
sleep 10
rmdir /tmp/my_lock_dir
fi
如果您有过时的锁定,则会出现问题,这意味着锁定存在但没有关联的进程。你的cron永远不会跑。
为什么要使用目录?因为mkdir是一个原子操作。一次只有一个进程可以创建目录,所有其他进程都会出错。这甚至适用于共享文件系统,甚至可能在不同的操作系统类型之间。
答案 3 :(得分:4)
如果要检查进程是否存在,只需查看
的输出 ps aux | grep your_script_name
如果它在那里,它还没死......
正如评论和其他答案中所指出的,使用存储在lockfile中的PID更加安全,是大多数应用程序采用的标准方法。我这样做是因为它很方便,我几乎从来没有看到过角落的情况(例如在cron
执行时编辑文件)。
答案 4 :(得分:4)
将您的pid存储在mylockFile
中。当您需要检查时,使用从文件中读取的pid查找ps以查找进程。如果存在,则表示您的脚本正在运行。
答案 5 :(得分:3)
如果使用锁定文件,则应确保始终删除锁定文件。你可以用'陷阱'来做到这一点:
if ( set -o noclobber; echo "locked" > "$lockfile") 2> /dev/null; then
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
echo "Locking succeeded" >&2
rm -f "$lockfile"
else
echo "Lock failed - exit" >&2
exit 1
fi
noclobber选项使得创建lockfile原子,就像使用目录一样。
答案 6 :(得分:3)
作为一个单行,如果你不想使用锁文件(例如只读文件系统的b / c /等)
test "$(pidof -x $(basename $0))" != $$ && exit
它检查带有脚本名称的PID的完整列表是否等于当前PID。 “-x”还会检查shell脚本的名称。
Bash使它更短更快:
[[ "$(pidof -x $(basename $0))" != $$ ]] && exit
答案 7 :(得分:1)
在某些情况下,您可能希望能够区分谁在运行脚本并允许一些并发但不是全部。在这种情况下,您可以使用per-user,per-tty或cron特定的锁。
您可以使用$ USER等环境变量或tty
等程序的输出来创建文件名。对于cron
,您可以在crontab
文件中设置变量并在脚本中对其进行测试。
答案 8 :(得分:1)
我今天试图解决这个问题,我想出了以下内容:
COMMAND_LINE="$0 $*"
JOBS=$(SUBSHELL_PID=$BASHPID; ps axo pid,command | grep "${COMMAND_LINE}" | grep -v $$ | g rep -v ${SUBSHELL_PID} | grep -v grep)
if [[ -z "${JOBS}" ]]
then
# not already running
else
# already running
fi
这依赖于$BASHPID
,它包含子shell中的PID(子shell中的$$
是父pid)。但是,这依赖于Bash v4,我需要在具有Bash v3.2.48的OSX上运行它。我最终提出了另一种解决方案,它更清洁:
JOBS=$(sh -c "ps axo pid,command | grep \"${COMMAND_LINE}\" | grep -v grep | grep -v $$")
答案 9 :(得分:0)
你可以随时:
if ps -e -o cmd | grep scriptname > /dev/null; then
exit
fi
但我自己喜欢锁文件,所以如果没有锁文件,我也不会这样做。
答案 10 :(得分:0)
你可以使用这个:
pgrep -f "/bin/\w*sh .*scriptname" | grep -vq $$ && exit
答案 11 :(得分:0)
由于尚未提及套接字解决方案,因此值得指出套接字可用作有效的互斥锁。套接字创建是一个原子操作,就像Gunstick指出的mkdir
一样,所以套接字适合用作锁或互斥锁。
Solo与目前提到的其他技术相比具有一个优势,即检查是在脚本之外完成的,您只想运行一个副本。如果脚本已在运行,那么该脚本的第二个实例将永远不会启动。这与隔离受锁保护的脚本内的代码块相反。编辑:如果在cron作业中使用flock
而不是在脚本内部,那么您也可以使用它来阻止脚本的第二个实例启动 - 请参阅下面的示例。
以下是您如何在cron中使用它的示例:
*/5 * * * * solo -port=3801 /path/to/script.sh args args args
# "/path/to/script.sh args args args" is only called if no other instance of
# "/path/to/script.sh" is running, or more accurately if the socket on port 3801
# is not open. Distinct port numbers can be used for different programs so that
# if script_1.sh is running it does not prevent script_2.sh from starting, I've
# used the port range 3801 to 3810 without conflicts. For Linux non-root users
# the valid port range is 1024 to 65535 (0 to 1023 are reserved for root).
* * * * * solo -port=3802 /path/to/script_1.sh
* * * * * solo -port=3803 /path/to/script_2.sh
# Flock can also be used in cron jobs with a distinct lock path for different
# programs, in the example below script_3.sh will only be started if the one
# started a minute earlier has already finished.
* * * * * flock -n /tmp/path.to.lock -c /path/to/script_3.sh
链接:
希望这有帮助。
答案 12 :(得分:0)
您可以使用this。
我会在这里无耻地复制粘贴解决方案,因为这是两个问题的答案(我认为它实际上更适合这个问题)。
<强> 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