yonran yonran - 4 months ago 12
Bash Question

How can bash script do the equivalent of Ctrl-C to a background task?

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?

Answer

Read this : How to send a signal SIGINT from script to script ? BASH

Also from info bash

   To facilitate the implementation of the user interface to job  control,
   the operating system maintains the notion of a current terminal process
   group ID.  Members of this process group (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-gen‐
   erated signals. 

So bash differentiates background processes from foreground processes by the process group ID. If the process group id is equal to process id, then the process is a foreground process, and will terminate when it receives a SIGINT signal. Otherwise it will not terminate (unless it is trapped).

You can see the process group Id with

ps x -o  "%p %r %y %x %c "

Thus, when you run a background process (with &) from within a script, it will ignore the SIGINT signal, unless it is trapped.

However, you can still kill the child process with other signals, such as SIGKILL, SIGTERM, etc.

For example, if you change your script to the following it will successfully kill the child process:

#!/bin/bash

if [ "$1" = "--child" ]; then
  sleep 1000
elif [ "$1" = "--parent" ]; then
  "$0" --child &
  for child in $(jobs -p); do
    echo kill "$child" && kill "$child"
  done
  wait $(jobs -p)

  else
  echo "Must be invoked with --child or --parent."
fi

Output:

$ ./test.sh --parent
kill 2187
./test.sh: line 10:  2187 Terminated              "$0" --child