17

For testing purposes I have this shell script

#!/bin/bash
echo $$
find / >/dev/null 2>&1

Running this from an interactive terminal, ctrl+c will terminate bash, and the find command.

$ ./test-k.sh
13227
<Ctrl+C>
$ ps -ef |grep find
$

Running it in the background, and killing the shell only will orphan the commands running in the script.

$ ./test-k.sh &
[1] 13231
13231
$ kill 13231
$ ps -ef |grep find
nos 13232     1  3 17:09 pts/5    00:00:00 find /
$

I want this shell script to terminate all its child processes when it exits regardless of how it's called. It'll eventually be started from a python and java application - and some form of cleanup is needed when the script exits - any options I should look into or any way to rewrite the script to clean itself up on exit?

nos
  • 223,662
  • 58
  • 417
  • 506

6 Answers6

15

I would do something like this:

#!/bin/bash
trap : SIGTERM SIGINT

echo $$

find / >/dev/null 2>&1 &
FIND_PID=$!

wait $FIND_PID

if [[ $? -gt 128 ]]
then
    kill $FIND_PID
fi

Some explanation is in order, I guess. Out the gate, we need to change some of the default signal handling. : is a no-op command, since passing an empty string causes the shell to ignore the signal instead of doing something about it (the opposite of what we want to do).

Then, the find command is run in the background (from the script's perspective) and we call the wait builtin for it to finish. Since we gave a real command to trap above, when a signal is handled, wait will exit with a status greater than 128. If the process waited for completes, wait will return the exit status of that process.

Last, if the wait returns that error status, we want to kill the child process. Luckily we saved its PID. The advantage of this approach is that you can log some error message or otherwise identify that a signal caused the script to exit.

As others have mentioned, putting kill -- -$$ as your argument to trap is another option if you don't care about leaving any information around post-exit.

For trap to work the way you want, you do need to pair it up with wait - the bash man page says "If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes." wait is the way around this hiccup.

You can extend it to more child processes if you want, as well. I didn't really exhaustively test this one out, but it seems to work here.

$ ./test-k.sh &
[1] 12810
12810
$ kill 12810
$ ps -ef | grep find
$
Carl Norum
  • 219,201
  • 40
  • 422
  • 469
  • 1
    The conventional no-op command is "`:`". – ephemient Oct 29 '09 at 17:21
  • If I launch the process in background, then it is not killed. If I do not launch it in the background, the spawned child process does get killed – Santosh Tiwari Jun 14 '11 at 20:32
  • Beware of a shortcoming of this (and most other methods I found): if you kill -9 the parent no clean up will be performed. As a workaround in some cases you can have the children monitor the health of their parent. – ndemou Oct 14 '15 at 16:12
9

Was looking for an elegant solution to this issue and found the following solution elsewhere.

trap 'kill -HUP 0' EXIT

My own man pages say nothing about what 0 means, but from digging around, it seems to mean the current process group. Since the script get's it's own process group, this ends up sending SIGHUP to all the script's children, foreground and background.

phemmer
  • 6,882
  • 3
  • 33
  • 31
  • This seems to work nicely (tried by killing the script with ctrl-C and closing the terminal) but I'm afraid of hidden shortcomings. Any bash guru willing to share his opinion? – ndemou Oct 14 '15 at 16:13
  • 1
    http://stackoverflow.com/a/22644006/301717 suggest a similar answer but seems more robust – Jérôme Pouiller Aug 23 '16 at 14:48
6

Send a signal to the group. So instead of kill 13231 do:

kill -- -13231

If you're starting from python then have a look at: http://www.pixelbeat.org/libs/subProcess.py which shows how to mimic the shell in starting and killing a group

pixelbeat
  • 30,615
  • 9
  • 51
  • 60
3

@Patrick's answer almost did the trick, but it doesn't work if the parent process of your current shell is in the same group (it kills the parent too).

I found this to be better:

trap 'pkill -P $$' EXIT

See here for more info.

Community
  • 1
  • 1
teh_senaus
  • 1,394
  • 16
  • 25
1

Just add a line like this to your script:

trap "kill $$" SIGINT

You might need to change 'SIGINT' to 'INT' on your setup, but this will basically kill your process and all child processes when you hit Ctrl-C.

Vlad the Impala
  • 15,572
  • 16
  • 81
  • 124
0

The thing you would need to do is trap the kill signal, kill the find command and exit.

ennuikiller
  • 46,381
  • 14
  • 112
  • 137