我有一个bash脚本来测试服务器在负载下的执行情况。
num=1
if [ $# -gt 0 ]; then
num=$1
fi
for i in {1 .. $num}; do
(while true; do
{ time curl --silent 'http://localhost'; } 2>&1 | grep real
done) &
done
wait
当我按下Ctrl-C时,主进程退出,但后台循环继续运行。如何让它们全部退出?或者是否有更好的方法来产生并行执行的可配置数量的逻辑循环?
答案 0 :(得分:41)
这是一个更简单的解决方案 - 只需在脚本顶部添加以下行:
trap "kill 0" SIGINT
Killing 0
将信号发送到当前进程组中的所有进程。
答案 1 :(得分:3)
杀死子弹的一种方法,但不是自我:
kill $(jobs -p)
答案 2 :(得分:2)
你需要使用job control,遗憾的是,它有点复杂。如果这些是您期望运行的唯一后台作业,则可以运行如下命令:
jobs \
| perl -ne 'print "$1\n" if m/^\[(\d+)\][+-]? +Running/;' \
| while read -r ; do kill %"$REPLY" ; done
jobs
以如下格式打印所有活动作业(正在运行的作业,以及最近完成或已终止的作业)的列表:
[1] Running sleep 10 &
[2] Running sleep 10 &
[3] Running sleep 10 &
[4] Running sleep 10 &
[5] Running sleep 10 &
[6] Running sleep 10 &
[7] Running sleep 10 &
[8] Running sleep 10 &
[9]- Running sleep 10 &
[10]+ Running sleep 10 &
(这些是我通过运行for i in {1..10} ; do sleep 10 & done
启动的工作。)
perl -ne ...
是我使用Perl来提取正在运行的作业的作业号码;如果您愿意,显然可以使用不同的工具。如果jobs
具有不同的输出格式,则可能需要修改此脚本;但上面的输出也在Cygwin上,所以它很可能和你的相同。
read -r
从标准输入读取“原始”行,并将其保存到变量$REPLY
中。 kill %"$REPLY"
将类似于kill %1
,它会“杀死”(发送中断信号)作业编号1.(不要与kill 1
混淆,这将导致进程< / em> number 1.)while read -r ; do kill %"$REPLY" ; done
一起检查Perl脚本打印的每个作业号,然后将其杀死。
顺便说一下,你的for i in {1 .. $num}
将无法达到你的预期,因为在参数扩展之前,处理大括号展开,所以你所拥有的就等于for i in "{1" .. "$num}"
。 (无论如何,你不能在支架扩展内部有白色空间。)不幸的是,我不知道一个干净的选择;我认为您必须执行类似for i in $(bash -c "{1..$num}")
的操作,或者切换到算术for
- 循环或诸如此类的东西。
顺便说一句,你不需要在括号中包装你的while循环; &
已经导致作业在子shell中运行。
答案 3 :(得分:1)
答案有点晚,但对我来说,kill 0
或kill $(jobs -p)
之类的解决方案太过分了(杀死所有子进程)。
如果您只想整理一个特定的子进程(及其子进程),那么更好的解决方案是使用子进程的PID按进程组(PGID)终止,如下所示:>
set -m
./some_child_script.sh &
some_pid=$!
kill -- -${some_pid}
首先,set -m
命令将启用作业管理(如果尚未启用),这一点很重要,因为否则所有命令,子shell等都将与父级分配到同一进程组。脚本(与在终端中手动运行命令时不同),并且kill只会给出“没有这样的过程”错误。需要在运行要作为一个组进行管理的后台命令之前调用此命令(或者,如果有多个脚本,请在脚本启动时调用它)。
第二,请注意kill
的参数为负,这表示您要终止整个进程组。默认情况下,进程组ID与组中的第一个命令相同,因此我们可以通过在用$!
获取的PID前面简单添加一个减号来获得它。如果需要在更复杂的情况下获取进程组ID,则需要使用ps -o pgid= ${some_pid}
,然后在其中添加减号。
最后,请注意使用选项--
的显式结尾,这一点很重要,因为否则进程组参数将被视为选项(信号号),而kill
则会抱怨它没有足够的参数。仅当进程组参数是您要终止的第一个参数时,才需要使用此参数。
这是后台超时过程以及如何尽可能清除的简化示例:
#!/bin/bash
# Use the overkill method in case we're terminated ourselves
trap 'kill $(jobs -p | xargs)' SIGINT SIGHUP SIGTERM EXIT
# Setup a simple timeout command (an echo)
set -m
{ sleep 3600; echo "Operation took longer than an hour"; } &
timeout_pid=$!
# Run our actual operation here
do_something
# Cancel our timeout
kill -- -${timeout_pid} >/dev/null 2>&1
wait -- -${timeout_pid} >/dev/null 2>&1
printf '' 2>&1
在所有合理的情况下,这应该干净地取消这种简单的超时;唯一无法处理的情况是脚本立即终止(kill -9
),因为它没有机会进行清理。
我还添加了wait
,后跟无操作(printf ''
),这是为了抑制由kill
命令引起的“终止”消息,这有点骇人听闻,但以我的经验足够可靠。
答案 4 :(得分:0)
这是我的最终解决方案。我正在使用数组变量跟踪子shell进程ID,并捕获Ctrl-C信号以杀死它们。
declare -a subs #array of subshell pids
function kill_subs() {
for pid in ${subs[@]}; do
kill $pid
done
exit 0
}
num=1 if [ $# -gt 0 ]; then
num=$1 fi
for ((i=0;i < $num; i++)); do
while true; do
{ time curl --silent 'http://localhost'; } 2>&1 | grep real
done &
subs[$i]=$! #grab the pid of the subshell
done
trap kill_subs 1 2 15
wait