tags:

views:

283

answers:

4

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?

A: 

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

ennuikiller
`kill` sends `SIGTERM` by default. `SIGKILL` is un-catchable.
Carl Norum
A: 

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.

Goose Bumper
instead of 'kill $$', you probably ought to send TERM to the pid of the find command!
William Pursell
`$!` maybe, though you probably want finer control than that.
ephemient
+2  A: 

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
Will every command in this script belong to that process group ?
nos
If started from a shell yes, otherwise you'll need to start as done in subProcess.py
pixelbeat
+2  A: 

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
The conventional no-op command is "`:`".
ephemient
Hot. Changing it now.
Carl Norum