我有以下 2 个脚本。
script1.sh
:
#!/usr/bin/env bash
set -x
set -e
./script2.sh &
pid=$!
max_retry=15
counter=0
until curl --silent --head --fail localhost:8000; do
if ! jobs %1; then
echo "Server quit unexpectedly before reporting successful status"
exit 1
fi
if [[ $counter -eq $max_retry ]]; then
echo "Server never reported healthy status"
kill -INT $pid
wait $pid
exit 1
fi
echo "Server not ready. Sleeping for 1 second"
sleep 1
counter=$((counter + 1))
done
echo "Server responded healthy"
kill -INT $pid
wait $pid
echo "Done"
script2.sh
:
#!/usr/bin/env bash
set -x
set -e
docker run \
-p 8000:80 \
nginxdemos/hello
当我运行 时./script1.sh
,我希望脚本script2.sh
在后台执行,运行curl
直到成功,然后script2.sh
以终止SIGINT
。然而,这种情况并没有发生。第 26行kill
不会终止脚本。相反,script2.sh
继续运行,直到我按Ctrl
+ C
。
为什么会发生这种情况?如何script2.sh
正确终止并将SIGINT
信号传播到任何子进程?
答案1
从低级操作系统的角度来看,正确的方法是将 PID 的负值传递给kill
,指定进程组 ID (PGID)。
不幸的是,通常情况下,您想要通过组 ID 杀死的进程与想要杀死它的父进程位于同一组中。不过,整个组都将终止,包括父组。
这是一个愚蠢的 Unix 问题,只能通过 hack 来解决。
我有一个script1.sh
背景script2.sh
,与你的相似。docker
我没有命令,而是sleep 3600
在script2.sh
.如果我杀了script2.sh
,他们sleep 3600
就还活着。
script2.sh
:
#!/bin/sh
sleep 3600
在 中script1.sh
,我使用 Awk 程序来查找该进程的所有后代script2.sh
。
script1.sh
:
#!/bin/sh
./script2.sh &
pid=$!
sleep 1
descendants=$(awk -f descendants.awk $pid)
kill -TERM $pid
for desc in $descendants; do
kill -TERM $desc
done
# wait for script2.sh by pid
wait $pid
echo done
这是我的descentants.awk
:
BEGIN {
# symbolic names for "ps -efj" columns
PID=2
PPID=3
root_process = ARGV[1]
while (("ps -efj" | getline) > 0)
parent[$PID] = $PPID
for (child in parent) {
pid = child;
do {
par = parent[pid]
if (par == root_process) {
print child
break
}
pid = par
} while (par != 0 && par != 1)
}
}
这是通过基于输出的第 2 列和第 3 列构建关联哈希来实现的ps -efj
:该哈希为我们提供了系统中任何给定 PID 的父级。然后我们遍历所有的 pid。对于每个 pid,我们通过沿着亲缘链追溯其祖先。我们打印每个将给定 pid 作为直接或间接祖先的 pid。
这不会找到父母死亡并因此被重新设置为 PID 1 的后代(在我们的 Awk 脚本收集亲子关系数据之前)。
就像如果script2.sh
放入sleep 3600 &
后台然后退出,Awk 脚本将显示为空;将sleep
继续运行,但其 PPID 为 1。
换句话说,我们假设该脚本及其后代表现良好。如果有任何东西自愿死亡,它会干干净净地死去,并且小心翼翼地不留下后代。如果我们突然杀掉它,我们就会照顾到后代。
PS 我正在 GNU Linux 上工作。我通过将输出与pstree -Tp
. -T
意思是隐藏线程。这是我们不做的另一件事;我们不讨论线程。我们假设如果我们杀死一个进程,它的线程将由线程库处理。线程实际上是 Linux 中的进程。
示例:这是输出的一部分pstree -Tp
:
│ ├─firefox(22690)─┬─Isolated Web Co(22776)
│ │ ├─Isolated Web Co(22880)
│ │ ├─Privileged Cont(22870)
│ │ ├─Socket Process(22748)
│ │ ├─Web Content(22907)
│ │ ├─Web Content(22934)
│ │ ├─Web Content(27810)
│ │ └─WebExtensions(22820)
这是脚本的输出:
$ awk -f descendants.awk 22690
22820
22880
22907
22776
22870
22934
27810
22748
它发现了相同的八个进程。子树深度超过 1 的另一个示例:
│ ├─gnome-terminal-(6802)───bash(6814)─┬─git(365)───wish(368)─┬─aspell(382)
│ │ │ └─git-diff-index(15474)
│ │ ├─git(26636)───wish(26641)───aspell(26650)
│ │ ├─txr(15717)
│ │ └─vi(933)
$ awk -f descendants.awk 6802
365
26641
368
382
15474
26636
26650
933
15717
6814