如何确保我的 bash 脚本尚未运行?
Posted
技术标签:
【中文标题】如何确保我的 bash 脚本尚未运行?【英文标题】:How do I make sure my bash script isn't already running? 【发布时间】:2010-11-29 06:49:00 【问题描述】:我有一个 bash 脚本,我想从 cron 每 5 分钟运行一次...但有可能该脚本的上一次运行尚未完成...在这种情况下,我希望新的运行退出.我不想只依赖 /tmp 中的一个锁定文件。我想确保在我尊重锁定文件(或其他任何东西)之前该进程正在实际运行......
这是我到目前为止从互联网上偷来的东西......我如何让它变得更聪明一点?还是有更好的完全不同的方法?
if [ -f /tmp/mylockFile ] ; then
echo 'Script is still running'
else
echo 1 > /tmp/mylockFile
/* Do some stuff */
rm -f /tmp/mylockFile
fi
【问题讨论】:
Quick-and-dirty way to ensure only one instance of a shell script is running at a time 的可能重复项 【参考方案1】:如果要检查进程是否存在,只需查看
的输出ps aux | grep your_script_name
如果它在那里,它还没有死……
正如 cmets 和其他答案中所指出的,使用存储在锁定文件中的 PID 更安全,并且是大多数应用程序采用的标准方法。我这样做是因为它很方便,而且在实践中我几乎从未见过极端情况(例如,在执行 cron
时编辑文件)。
【讨论】:
另外,您可以将当前 BASH 进程的 PID 存储在该锁定文件中。 BASH 提供了一个变量 '$$' (减去引号),它给出了那个数字。请参阅此处:tldp.org/LDP/abs/html/internalvariables.html 了解其中一些变量 -1 如果“emacs your_script_name”是正在运行的进程之一怎么办? 你说的都对。存储在文件中的 PID 将是一种更好的方法。我只是倾向于这样做,因为我很懒惰,我的脚本不是关键任务,而且它通常可以工作(很少见我会在 cron 执行时实际编辑脚本)。【参考方案2】:将您的 pid 存储在 mylockFile
中。当您需要检查时,使用您从文件中读取的 pid 查找 ps 进程。如果存在,则说明您的脚本正在运行。
【讨论】:
【参考方案3】:你总是可以:
if ps -e -o cmd | grep scriptname > /dev/null; then
exit
fi
但我自己喜欢锁文件,所以如果没有锁文件我也不会这样做。
【讨论】:
根据需要的可移植性,您可能需要调整 ps 选项,并且您可以将 -q 添加到 grep 而不是 /dev/null 不够同事证明:有人可能正在运行“更少的脚本名称”,这足以阻止脚本运行 mobrule:是的,从来没想过,因为我永远不会这样做 ;-) 锁文件更好。【参考方案4】:# 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 上以不同的方式工作,等等......
【讨论】:
为什么是cat /dev/null >> $lf
而不是touch $lf
?没有循环时为什么要sleep
?
touch(1)
也不错。睡眠只是为了演示,让我的脚本片段可以自己测试。
(也就是说,脚本一结束,pid就会消失)【参考方案5】:
如果您有幸在您的发行版中获得它,您可能想查看flock
命令的手册页。
【讨论】:
与其他一些建议的解决方案不同,此解决方案最干净地避免了检查和获取锁之间的竞争条件。如果你有 'flock(1)'(在 debian/Ubuntu 等中带有 util-linux),一定要使用它。对于 OP 用例,您需要一个短暂的超时时间(例如 -w 1)。如果命令已经在运行,flock 会在 1 秒后放弃,不会运行命令,否则会获取锁并启动命令。 OP 可以使用-n, --nb, --nonblock Fail (with an exit code of 1) rather than wait if the lock cannot be immediately acquired.
代替使用 -w 1
超时并在 1 秒后退出,请参阅我的答案中的示例,该示例在此主题处于活动状态后不幸添加了。【参考方案6】:
在某些情况下,您可能希望能够区分谁在运行脚本并允许一些并发但不是全部。在这种情况下,您可以使用 per-user、per-tty 或 cron-specific 锁。
您可以使用 $USER 等环境变量或tty
等程序的输出来创建文件名。对于cron
,您可以在crontab
文件中设置一个变量并在您的脚本中对其进行测试。
【讨论】:
【参考方案7】:永远不要使用锁文件总是使用锁目录。 在您的特定情况下,它并不那么重要,因为脚本的启动时间间隔为 5 分钟。但是,如果您曾经将此代码重用于网络服务器 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 是原子操作。一次只有一个进程可以创建目录,所有其他进程都会出错。这甚至适用于共享文件系统,甚至可能适用于不同的操作系统类型。
【讨论】:
锁定文件在 NFS 中不起作用——这在共享主机中很常见。出于同样的原因,我们经常使用锁定目录。【参考方案8】:如果您使用锁定文件,则应确保始终删除锁定文件。你可以用'trap'来做到这一点:
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 的创建原子化,就像使用目录一样。
【讨论】:
【参考方案9】:你可以用这个:
pgrep -f "/bin/\w*sh .*scriptname" | grep -vq $$ && exit
【讨论】:
【参考方案10】:作为一个单行,如果您不想使用锁定文件(例如,只读文件系统的 b/c/ 等)
test "$(pidof -x $(basename $0))" != $$ && exit
它会检查带有脚本名称的 PID 的完整列表是否等于当前 PID。 "-x" 还检查 shell 脚本的名称。
Bash 让它变得更短更快:
[[ "$(pidof -x $(basename $0))" != $$ ]] && exit
【讨论】:
【参考方案11】:我今天试图解决这个问题,我想出了以下内容:
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 $$")
【讨论】:
【参考方案12】:由于尚未提及套接字解决方案,因此值得指出的是,套接字可以用作有效的互斥体。套接字创建是一个原子操作,就像 Gunstick 指出的 mkdir
一样,因此套接字适合用作锁或互斥体。
Tim Kay 的 Perl 脚本 'Solo' 是一个非常小而有效的脚本,可确保在任何时候只能运行一个脚本的副本。它是专门为与 cron 作业一起使用而设计的,尽管它也适用于其他任务,而且我已经非常有效地将它用于非 crob 作业。
Solo 与目前提到的其他技术相比有一个优势,即检查是在您只想运行一份副本的脚本之外完成的。如果脚本已经在运行,那么该脚本的第二个实例将永远不会启动。这与在受锁保护的脚本内隔离代码块相反。编辑:如果 flock
用于 cron 作业,而不是在脚本内部,那么您也可以使用它来防止脚本的第二个实例启动 - 请参见下面的示例。
这是一个如何将它与 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
链接:
个人网页:http://timkay.com/solo/ 单独脚本:http://timkay.com/solo/solo希望这会有所帮助。
【讨论】:
【参考方案13】:您可以使用this。
我将在这里无耻地复制粘贴解决方案,因为它是两个问题的答案(我认为它实际上更适合这个问题)。
用法
-
包括 sh_lock_functions.sh
使用 sh_lock_init 初始化
使用 sh_acquire_lock 锁定
使用 sh_check_lock 检查锁
使用 sh_remove_lock 解锁
脚本文件
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
特点
使用文件、目录和进程 ID 的组合来锁定以确保进程尚未运行 您可以检测脚本是否在解除锁定之前停止(例如进程终止、关闭、错误等) 您可以检查锁定文件,并在锁定丢失时使用它来触发进程关闭 详细,输出错误消息以便于调试【讨论】:
以上是关于如何确保我的 bash 脚本尚未运行?的主要内容,如果未能解决你的问题,请参考以下文章
在 linux bash 中运行 php 脚本(php 函数)
如何在 Mac 上从 Dockerfile 运行 bash 脚本