Is there any way to invoke a subprocess so that it and all its descendants are sent an interrupt, just as if you Ctrl-C a foreground task? I’m trying to kill a launcher script that invokes a long-running child. I’ve tried kill -SIGINT $child
(which doesn’t send the interrupt to its descendants so is a no-op) and kill -SIGINT -$child
(which works when invoked interactively but not when running in a script).
Here’s a test script. The long-running script is test.sh --child
. When you call test.sh --parent
, it invokes test.sh --child &
and then tries to kill it. How can I make the parent kill the child successfully?
#!/bin/bash
if [ "$1" = "--child" ]; then
sleep 1000
elif [ "$1" = "--parent" ]; then
"$0" --child &
for child in $(jobs -p); do
echo kill -SIGINT "-$child" && kill -SIGINT "-$child"
done
wait $(jobs -p)
else
echo "Must be invoked with --child or --parent."
fi
I know that you can modify the long-running child to trap
signals, send them to its subprocess, and then wait (from
Bash script kill background (grand)children on Ctrl+C), but is there any way without modifying the child script?
For anyone wondering, this is how you launch childs in the background and kill them on ctrl+c:
#!/usr/bin/env bash
command1 &
pid[0]=$!
command2 &
pid[1]=$!
trap "kill ${pid[0]} ${pid[1]}; exit 1" INT
wait
You can keep using SIGINT
with background tasks with an easy little twist: Put your asynchronous subprocess call in a function or {
}
, and give it setsid
so it has its own process group.
Here's your script keep it's whole first intention:
using and propagating SIGINT
and not using another signal
modifying only the calling from: "$0" --child &
to { setsid "$0" --child; } &
adding the code necessary to get the PID of your child instance, which is the only process in the background subshell.
Here's your code:
#!/bin/bash
if [ "$1" = "--child" ]; then
sleep 1000
elif [ "$1" = "--parent" ]; then
{ setsid "$0" --child; } &
subshell_pid=$!
pids=$(ps -ax -o ppid,pid --no-headers |
sed -r 's/^ +//g;s/ +/ /g' |
grep "^$subshell_pid " | cut -f 2 -d " ");
for child in $pids; do
echo kill -SIGINT "-$child" && kill -SIGINT "-$child"
done
wait $subshell_pid
else
echo "Must be invoked with --child or --parent."
Here's the important doc part from bash manual
Process group id effect on background process (in Job Control section of doc):
[...] processes whose process group ID is equal to the current terminal process group ID [..] receive keyboard-generated signals such as SIGINT. These processes are said to be in the foreground. Background processes are those whose process group ID differs from the terminal's; such processes are immune to keyboard-generated signals.
Default handler for SIGINT
and SIGQUIT
(in Signals section of doc):
Non-builtin commands run by bash have signal handlers set to the values inherited by the shell from its parent. When job control is not in effect, asynchronous commands ignore SIGINT and SIGQUIT in addition to these inherited handlers.
and about modification of traps (in trap
builtin doc):
Signals ignored upon entry to the shell cannot be trapped or reset.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With