我确实在 Bash 脚本中启动了一个长时间运行的后台进程。将进程发送到后台后,我将 PID 号保存在变量中,并在必要时使用该 PID 号来终止该进程。
但是,如果该后台进程在我的脚本杀死它之前以某种方式终止,并且系统为新创建的进程分配相同的 PID 号,那么当我使用该数字杀死该后台进程时,此操作可能会杀死该新创建的进程(取决于权限) , 当然)。
我知道,使用过的 PID 号不会在短时间内分配给任何新创建的进程,但我的脚本运行了数周,所以这是可能的。
怎样才能避免这样的事故发生呢?
答案1
正如评论中所建议的,该pkill
实用程序可能有用。
既然你说“bash脚本”,你很可能必须运行pkill bash
- 这是你不应该做的事情。
相反,您可以使用pkill -f <name>
,它将使用完整的进程名称来匹配。因此,假设您的任务是bash /home/me/my_script.sh
,您可以使用以下内容:
pkill -f -e my_script.sh
这-e
是可选的,只是打印出被杀死的内容。
选择:
将以下脚本保存为/usr/bin/mykill
(或任何您想要的位置):
#!/bin/bash
mypid="$1"
if [[ ! -f /proc/$mypid/cmdline ]]; then
echo "Process ID not found."
exit 1
else
echo "About to kill $(cat /proc/$mypid/cmdline)"
echo "Press enter if you want to kill that process"
read -p "Press CTRL-C if you don't want that"
kill $mypid
fi
并将其运行为mykill <pid>
答案2
如果您的后台进程在您的控制之下,请在其命令行中添加额外的标识作为标签,您可以将其副本与 Pid 一起保留,然后签入ps -o args myPid
。
我使用像这样的选项--unique "${myTag}"
我从uuidgen
, 或 a导出 myTagdate
至纳秒精度。如果是 ssh 作业,请包含本地主机名。
如果您无法引入新选项:
.. 用于date +%s
获取作业的开始时间,并与 Pid 一起存储。
..用于ps -o etimes
获取该过程经过的时间(以秒为单位)。
.. 与当前比较date +%s
(可能有几秒钟的误差)。
任何一种方法与 Pid 结合使用时,错误概率都应该可以忽略不计。
答案3
我将 PID 与 /proc/<PID> 的时间戳组合成一个 uniq id,而不用担心杀死错误的 PID。
保存$PID:
echo $PID $(stat --format %Z /proc/$PID/comm) > pid
安全地杀死 $PID:
read PID TIMESTAMP < pid
[[ $(stat --format %Z /proc/$PID/comm) != $TIMESTAMP ]] || kill -SIGKILL $PID
这样,即使$PID被另一个进程回收,它的创建时间(/proc/$PID/comm的时间戳)也会不同,因此不可能重复。
PS:这[[ ... ]] || cmd
意味着如果[[ ]]
为真则不执行任何操作,否则运行cmd
。
编辑:我们确实有其他许多解决方法,但我想为什么不直接解决它呢?这应该是最简单的直接方式,不需要后台服务,不需要高级系统库,例如大多数容器都没有很好支持的systemd。我在一个需要中断其他正在进行的进程的产品中使用了这种方式。
EDIT2:当你杀死你的后台进程时,你最好在一个单独的进程组中启动你的后台进程,例如setsid background_process &
,然后在background_process的任何子进程中,你可以通过获取进程组id ps -o pgid= $$
,然后在killer端杀死进程组ID,然后所有子进程将被原子地杀死。否则,你杀死一个正常的进程ID,那么它的子进程仍然活着,即使使用pkill -P parant_pid
它仍然有机会让新的子进程逃脱。
答案4
我不同意这通常是不可能的。pkill
如果可以避免的话,我不建议通过名称杀死它(如果您合法地希望某个命令的多个实例有单独的超时怎么办?)。我不明白有关答案如何jobs
消除竞争条件;使用jobs
and thenkill
不是原子的。
但是,如果启用了作业控制,我们可以使用进程组 ID ( PGID
s ),或者使用子 shell 并通过进程父 ID ( PPID
) 进行终止(如果这不是一个选项)。请参阅我的帖子:https://unix.stackexchange.com/a/649320/464414
我只会摘录首选方法并将其粘贴到此处;请参阅上面的帖子以了解注释和替代方案。
更新:以下版本现在适用于管道
timeOut() {
checkArgs() { [ $(( ${1} )) -gt 0 -a "${*:2}" ]; }
jobControlEnabled() { expr "${-}" : '.*m' >/dev/null; }
terminalFDs() { [ -t 0 -a -t 1 ]; }
groupLeader() { sh -c 'expr `ps -o pgid= ${PPID}` : "${PPID}" >/dev/null;'; }
timeOutImpl() {
groupLeader || { echo "Job control error - not group leader!"; return -1; }
KILL_SUB="kill -- -`sh -c 'echo ${PPID}'`"
{ sleep ${1}; ${KILL_SUB}; } &
"${@:2}"; ${KILL_SUB}
}
checkArgs "${@}" || { echo "Usage: timeOut <delay> <command>"; return -1; }
if jobControlEnabled && terminalFDs; then
( timeOutImpl "${@}"; )
else
( set -m; ( timeOutImpl "${@}"; ); )
fi
}