【问题标题】:How do you kill all child processes without killing the parent如何在不杀死父进程的情况下杀死所有子进程
【发布时间】:2014-10-27 13:09:51
【问题描述】:

我有一个脚本,它在开始时运行一个后台进程,然后到最后需要停止该后台进程及其子进程,然后执行一些其他任务等,然后在必要时返回一个错误代码。我该怎么做呢?我已经看到了几个如何杀死包括父进程在内的整个树的示例(例如kill 0),但我希望主脚本继续运行并返回正确的错误代码。例如:

./some_process_with_child_processes &
./do_stuff
kill $! # doesnt kill child processes!
./do_other_stuff
exit 5

【问题讨论】:

  • 修改程序,让父级忽略信号而父级不忽略/
  • 立即想到了一些想法,在不了解您的代码库的情况下,您可以确保所有子进程都具有与父进程相关的唯一名称,例如,所有内容都带有前缀foo_project_child_,这样你就可以简单地使用 pkill -f foo_project_child_ 来处理事情,否则,你反对使用像 redis 等数据存储,你可以查询并找出你需要杀死哪些进程?
  • 如果可能的话,我宁愿不对所有 foo_project_etc* 实例执行全局 pkill,我希望它是独立的。我还没有研究过redis,我现在会。 @Ed我如何修改我的shell脚本不被杀死,但它的子进程会被杀死?这是我的问题。
  • @grasevski - 使用trap

标签: linux bash unix


【解决方案1】:

扩展 Ed Heal 的评论:

在父进程中,trap SIGTERM,eg

trap "echo received sigterm" SIGTERM

然后,当你想终止所有子进程时,在主脚本中做

kill -s SIGTERM 0

父级也会收到 SIGTERM,但这并不重要,因为它会捕获该信号。

您可能还会发现在子进程中为 EXIT 设置陷阱很有用,以便它们在收到 SIGTERM 时进行清理。

可以向子进程发送 SIGKILL,但这有点残酷,而且子进程将没有机会进行任何清理。


编辑

这里有一些脚本说明了父脚本如何单独杀死其子脚本,而不是使用kill -s SIGTERM 0 集体杀死。

陷阱测试

#!/bin/bash

# trap test
# Written by PM 2Ring 2014.10.23

myname=$(basename "$0")
child=sleeploop
delay=${1:-5}
loops=${2:-5}

sig=SIGTERM 
# sig=SIGKILL 
# sig=SIGINT 

# killed=False

signal_children()
{
    killed=True
    for ipid in "${pids[@]}"
    do 
        echo "Sending $sig to $ipid"
        kill -s $sig $ipid
    done
}

set_INT_trap()
{
    msg="echo -e \"\n$myname received ^C, sending $sig to children\""
    trap "$msg; signal_children" SIGINT
}

trap "echo \"bye from $myname\"" EXIT

pids=()
for i in A B C;do 
    echo "running $child $i $delay ..."
    ./$child $i $delay  &
    pid=$!
#     echo -e "$child$i PID = $pid\n"
    pids+=($pid)
done
# echo "all child PIDs: ${pids[@]}"

echo "$myname PID = $$"
echo; ps; echo

set_INT_trap $sig
trap "echo $myname Received SIGTERM; signal_children" SIGTERM

echo "$myname sleeping..."
sleep 18 &
wait $!
echo "$myname awake"

[[ $killed != True ]] && { echo "$myname sending $sig"; signal_children; }

echo "$myname finished"

睡眠循环

#!/bin/bash

# child script for traptest
# Written by PM 2Ring 2014.10.23

myname="$(basename "$0")$1"
delay=$2
loops=${3:-5}

set_trap()
{
    sig=$1
    trap "echo -e '\n$myname received $sig signal';exit 0" $sig
}

trap "echo \"bye from $myname\"" EXIT
set_trap SIGTERM
# set_trap SIGINT

#Select sleep mode
if false
then
    echo "$myname using foreground sleep"
    Sleep()
    {
        sleep $delay
    }
else
    echo "$myname using background sleep"
    Sleep()
    {
        sleep "$delay" &
        wait $!
    }
fi

#Time to snooze :)
for ((i=0; i<loops; i++));
do
    echo "$i: $myname sleeping for $delay"
    Sleep
done

echo "$myname terminated normally"

如果你想通过管道输出traptest,这甚至可以工作,例如尝试

{ ./traptest; echo &gt;&amp;2 exitcode $?; } | cat -n

【讨论】:

  • 这个问题是,如果我随后将我的脚本通过管道传输到另一个程序中,例如头、尾等,那么这些也会被杀死,返回码将为 143。这使得获取它很麻烦我脚本的正确返回码。
  • 在这种情况下,我认为您需要保存每个父子进程的 PID,以便您可以单独杀死它们。
【解决方案2】:

您需要在

之后立即捕获 $!(子 PID)的值
/some_process_with_child_processes &
pid=$!
./do_stuff
kill $pid
./do_other_stuff

【讨论】:

  • 这不会杀死它的子进程。
  • 这里的“它”是什么?后台工作的孩子?通常情况下,一份工作在死亡时确实会杀死它的孩子。如果它不这样做,可能它已经“守护”了自己或以相反的方式注册了信号处理程序。如果是这样的话,干净的修复可能是修改后台作业。你能做到吗?
  • 我同意您的观点,即彻底修复是修改后台作业以终止其进程。我会尝试这样做,否则我会放弃并选择像“kill 0”这样的hacky解决方案。
  • 不幸的是,另一个程序是一个糟糕的第 3 方脚本。我可能必须修复/重写它,因为似乎没有简单的方法可以将组中的其他进程(例如来自管道或周围的 shell)与该脚本产生的进程区分开来。
  • 如果有一种独立的方式来简单地包装那个糟糕的脚本会很好,这样被包装的脚本会杀死糟糕的脚本启动的所有内容,而不是别的。
最近更新 更多