确保只有一个 Bash 脚本实例正在运行的最佳方法是什么?[重复]
- 2024-10-12 10:28:00
- admin 原创
- 112
问题描述:
确保只有一个给定脚本实例正在运行的最简单/最佳方法是什么 - 假设它是 Linux 上的 Bash?
目前我正在做的事情:
ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh
但它有几个问题:
它将检查放在脚本之外
它不允许我从不同的帐户运行相同的脚本——有时我会希望这样做。
-C
仅检查进程名称的前 14 个字符
当然,我可以编写自己的 pidfile 处理,但我觉得应该有一种简单的方法来做到这一点。
解决方案 1:
咨询锁定已经使用了很长时间,并且可以在 bash 脚本中使用。我更喜欢简单的flock
(from util-linux[-ng]
) 而不是lockfile
(from procmail
)。并且始终记住在这些脚本中退出时使用陷阱(sigspec ==EXIT
或0
,捕获特定信号是多余的)。
2009 年,我发布了可锁定脚本样板(最初可在我的 wiki 页面上找到,现在可作为gist获得)。将其转换为每个用户一个实例很简单。使用它,您还可以轻松编写需要锁定或同步的其他场景的脚本。
为了您的方便,这里提到了样板。
#!/bin/bash
# SPDX-License-Identifier: MIT
## Copyright (C) 2009 Przemyslaw Pawelczyk <przemoc@gmail.com>
##
## This script is licensed under the terms of the MIT license.
## https://opensource.org/licenses/MIT
#
# Lockable script boilerplate
### HEADER ###
LOCKFILE="/var/lock/`basename $0`"
LOCKFD=99
# PRIVATE
_lock() { flock -$1 $LOCKFD; }
_no_more_locking() { _lock u; _lock xn && rm -f $LOCKFILE; }
_prepare_locking() { eval "exec $LOCKFD>\ "$LOCKFILE\ ""; trap _no_more_locking EXIT; }
# ON START
_prepare_locking
# PUBLIC
exlock_now() { _lock xn; } # obtain an exclusive lock immediately or fail
exlock() { _lock x; } # obtain an exclusive lock
shlock() { _lock s; } # obtain a shared lock
unlock() { _lock u; } # drop a lock
### BEGIN OF SCRIPT ###
# Simplest example is avoiding running multiple instances of script.
exlock_now || exit 1
# Remember! Lock file is removed when one of the scripts exits and it is
# the only script holding the lock or lock is not acquired at all.
解决方案 2:
如果脚本对所有用户都相同,则可以使用一种lockfile
方法。如果获取了锁,则继续,否则显示一条消息并退出。
举个例子:
[Terminal #1] $ lockfile -r 0 /tmp/the.lock
[Terminal #1] $
[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] lockfile: Sorry, giving up on "/tmp/the.lock"
[Terminal #1] $ rm -f /tmp/the.lock
[Terminal #1] $
[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] $
/tmp/the.lock
获取后,您的脚本将是唯一有权执行的脚本。完成后,只需移除锁定即可。以脚本形式,这可能看起来像:
#!/bin/bash
lockfile -r 0 /tmp/the.lock || exit 1
# Do stuff here
rm -f /tmp/the.lock
解决方案 3:
我认为flock
这可能是最简单(也是最难忘)的变体。我将其用于 cron 作业中,以自动编码dvd和cd
# try to run a command, but fail immediately if it's already running
flock -n /var/lock/myjob.lock my_bash_command
用于-w
超时或省略选项以等待锁定释放。最后,手册页显示了多个命令的一个很好的示例:
(
flock -n 9 || exit 1
# ... commands executed under lock ...
) 9>/var/lock/mylockfile
解决方案 4:
使用 bashset -o noclobber
选项并尝试覆盖一个公共文件。
flock
这种“bash 友好”技术在不可用或不适用时将会很有用。
一个简短的例子
if ! (set -o noclobber ; echo > /tmp/global.lock) ; then
exit 1 # the global.lock already exists
fi
# ... remainder of script ...
一个更长的例子
此示例将等待global.lock
文件但时间过长后会超时。
function lockfile_waithold()
{
declare -ir time_beg=$(date '+%s')
declare -ir time_max=7140 # 7140 s = 1 hour 59 min.
# poll for lock file up to ${time_max}s
# put debugging info in lock file in case of issues ...
while ! \ n (set -o noclobber ; \ n echo -e "DATE:$(date)
USER:$(whoami)
PID:$$" > /tmp/global.lock
) 2>/dev/null
do
if [ $(($(date '+%s') - ${time_beg})) -gt ${time_max} ] ; then
echo "Error: waited too long for lock file /tmp/global.lock" 1>&2
return 1
fi
sleep 1
done
return 0
}
function lockfile_release()
{
rm -f /tmp/global.lock
}
if ! lockfile_waithold ; then
exit 1
fi
trap lockfile_release EXIT
# ... remainder of script ...
这项技术在我长期运行的 Ubuntu 16 主机上可靠地发挥作用。主机定期排队许多 bash 脚本实例,这些脚本使用同一个系统范围的“锁定”文件协调工作。
(这与后来注意到的@Barry Kelly 的这篇帖子类似。)
解决方案 5:
我不确定是否存在任何一行强大的解决方案,因此您最终可能会自己动手。
锁文件并不完善,但比使用“ps | grep | grep -v”管道要好一些。
话虽如此,您可能考虑将流程控制与脚本分开 - 有一个启动脚本。或者,至少将其分解为保存在单独文件中的函数,因此您可能在调用者脚本中具有:
. my_script_control.ksh
# Function exits if cannot start due to lockfile or prior running instance.
my_start_me_up lockfile_name;
trap "rm -f $lockfile_name; exit" 0 2 3 15
在每个需要控制逻辑的脚本中。陷阱确保在调用者退出时删除锁文件,因此您不必在脚本的每个出口点上都编写此代码。
使用单独的控制脚本意味着您可以对边缘情况进行健全性检查:删除过时的日志文件、验证锁文件是否与当前正在运行的脚本实例正确关联、提供终止正在运行的进程的选项等等。这也意味着您更有可能ps
成功使用 grep 进行输出。ps-grep 可用于验证锁文件是否与正在运行的进程相关联。也许您可以以某种方式命名锁文件以包含有关进程的信息:用户、pid 等,稍后的脚本调用可以使用这些信息来确定创建锁文件的进程是否仍然存在。
解决方案 6:
我在 procmail 包依赖项中发现了这一点:
apt install liblockfile-bin
运行:dotlockfile -l file.lock
file.lock 将被创建。
解锁:dotlockfile -u file.lock
使用它来列出此包的文件/命令:dpkg-query -L liblockfile-bin
解决方案 7:
第一个测试例子
[[ $(lsof -t $0| wc -l) > 1 ]] && echo "At least one of $0 is running"
第二个测试例子
currsh=$0
currpid=$$
runpid=$(lsof -t $currsh| paste -s -d " ")
if [[ $runpid == $currpid ]]
then
sleep 11111111111111111
else
echo -e "
PID($runpid)($currpid) ::: At least one of \ "$currsh\ " is running !!!
"
false
exit 1
fi
解释
“lsof -t” 列出名为“$0”的当前正在运行的脚本的所有 pid。
命令“lsof”有两个好处。
忽略正在用编辑器(如 vim)编辑的 pid,因为 vim 编辑它的映射文件(如“.file.swp”)。
忽略当前正在运行的 shell 脚本分叉的 pid,这是大多数“grep”派生命令无法实现的。使用“pstree -pH pidnum”命令查看当前进程分叉状态的详细信息。
解决方案 8:
我还建议看一下chpst(runit 的一部分):
chpst -L /tmp/your-lockfile.loc ./script.name.sh
解决方案 9:
Ubuntu/Debian 发行版具有start-stop-daemon
与您描述的用途相同的工具。另请参阅/etc/init.d/skeleton以了解如何使用它来编写启动/停止脚本。
-- 诺亚
解决方案 10:
一行终极解决方案:
[ "$(pgrep -fn $0)" -ne "$(pgrep -fo $0)" ] && echo "At least 2 copies of $0 are running"
解决方案 11:
我遇到了同样的问题,于是想出了一个模板,它使用 lockfile、保存进程 ID 号的 pid 文件以及一个kill -0 $(cat $pid_file)
检查,以使中止的脚本不会停止下一次运行。这会在 /tmp 中创建一个 foobar-$USERID 文件夹,lockfile 和 pid 文件就存放在该文件夹中。
您仍然可以调用脚本并执行其他操作,只要将这些操作保留在 中即可alertRunningPS
。
#!/bin/bash
user_id_num=$(id -u)
pid_file="/tmp/foobar-$user_id_num/foobar-$user_id_num.pid"
lock_file="/tmp/foobar-$user_id_num/running.lock"
ps_id=$$
function alertRunningPS () {
local PID=$(cat "$pid_file" 2> /dev/null)
echo "Lockfile present. ps id file: $PID"
echo "Checking if process is actually running or something left over from crash..."
if kill -0 $PID 2> /dev/null; then
echo "Already running, exiting"
exit 1
else
echo "Not running, removing lock and continuing"
rm -f "$lock_file"
lockfile -r 0 "$lock_file"
fi
}
echo "Hello, checking some stuff before locking stuff"
# Lock further operations to one process
mkdir -p /tmp/foobar-$user_id_num
lockfile -r 0 "$lock_file" || alertRunningPS
# Do stuff here
echo -n $ps_id > "$pid_file"
echo "Running stuff in ONE ps"
sleep 30s
rm -f "$lock_file"
rm -f "$pid_file"
exit 0
解决方案 12:
我发现了一种处理“每个系统一个脚本副本”的相当简单的方法。但它不允许我从多个帐户运行脚本的多个副本(在标准 Linux 上)。
解决方案:
在脚本的开头,我给出了:
pidof -s -o '%PPID' -x $( basename $0 ) > /dev/null 2>&1 && exit
显然pidof 的工作原理如下:
它对程序名称没有限制
ps -C ...
它不需要我做
grep -v grep
(或任何类似的事情)
而且它不依赖于锁文件,对我来说这是一个很大的胜利,因为依赖它们意味着你必须添加对陈旧锁文件的处理 - 这并不复杂,但如果可以避免 - 为什么不呢?
至于使用“每个运行用户一份脚本副本”进行检查,我写了这个,但我对此并不太满意:
(
pidof -s -o '%PPID' -x $( basename $0 ) | tr ' ' '
'
ps xo pid= | tr -cd '[0-9
]'
) | sort | uniq -d
然后我检查它的输出 - 如果它是空的 - 那么就没有来自同一用户的脚本副本。
解决方案 13:
这是我们的标准位。它可以从脚本以某种方式终止后恢复,而无需清理其锁定文件。
如果进程正常运行,它会将进程 ID 写入锁定文件。如果在开始运行时找到锁定文件,它会从锁定文件中读取进程 ID 并检查该进程是否存在。如果进程不存在,它会删除过时的锁定文件并继续。只有当锁定文件存在且进程仍在运行时,它才会退出。退出时它会写入一条消息。
# lock to ensure we don't get two copies of the same job
script_name="myscript.sh"
lock="/var/run/${script_name}.pid"
if [[ -e "${lock}" ]]; then
pid=$(cat ${lock})
if [[ -e /proc/${pid} ]]; then
echo "${script_name}: Process ${pid} is still running, exiting."
exit 1
else
# Clean up previous lock file
rm -f ${lock}
fi
fi
trap "rm -f ${lock}; exit $?" INT TERM EXIT
# write $$ (PID) to the lock file
echo "$$" > ${lock}
解决方案 14:
来自你的脚本:
ps -ef | grep $0 | grep $(whoami)
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件