3

I'm trying to set up a shell script so that it runs background processes, and when I ctrl+C the shell script, it kills the children, then exits.

The best that I've managed to come up with is this. It appears that kill 0 -INT also kills the script before the wait happens, so the shell script dies before the children complete.

Any ideas on how I can make this shell script wait for the children to die after sending INT?

#!/bin/bash
trap 'killall' INT

killall() {
    echo **** Shutting down... ****
    kill 0 -INT
    wait # Why doesn't this wait??
    echo DONE
}

process1 &
process2 &
process3 &

cat # wait forever
Sven
  • 97,248
  • 13
  • 177
  • 225
slipheed
  • 131
  • 1
  • 2

2 Answers2

2

First, you should know that kill 0 send the signal to all process in current process group. (see kill(1) - Linux man page) My guess is that it is killing more than it should. This is why wait is not waiting, it is being terminated for some reason. My best bet to solve this problem is to iterate over running jobs from jobs -pr killing one by one: this way I assure the signal is sent only to child processes in that moment and nothing more.

To get this to work I did several tests, but I was stuck on sending SIGINT to child processes. They simple don't react to it! Searching the web I've found this answer on Stack Overflow:

https://stackoverflow.com/questions/2524937/how-to-send-a-signal-sigint-from-script-to-script-bash

So the real problem is that you cannot send SIGINT from one script to other, because in non-interactive shells the signal is ignored. I was not able to workaround this issue (using bash -i to call the child script does not to work).

I know you probably want to send SIGINT and wait for the child processes to shutdown gracefully, but if you do not mind to use SIGTERM (or any other signal but SIGINT) this is the best script I wrote:

#!/bin/bash
trap 'killall' INT

killall() {
    echo '**** Shutting down... ****'
    jobs -pr
    for i in $(jobs -pr); do
        kill -TERM $i
    done
    wait
    echo DONE
}

./long.sh &
./long.sh &
./long.sh &

cat # wait forever

To test, I've created this long.sh script:

#!/bin/bash
trap 'killall' TERM

echo "Started... $$" >> file.txt

killall() {
    # shutting down gracefully
    echo "Finished... $$" >> file.txt
    exit # don't forget this!
}

# infinite loop
while true; do echo loop >/dev/null ; done

In this last script I did not use sleep function because it creates a new process that was staying on memory even after the script finishes. In your script, you can call new processes but you should assure to broadcast the SIGTERM (or any other signal you've used) to all of them.

Diego Queiroz
  • 280
  • 1
  • 9
0

This seems to work. Be sure to use "/usr/bin/kill" and not the Bash built-in "kill".

[myles@marklar ~]$ /usr/bin/kill --version
kill from util-linux-2.13-pre7

Note that job control is not enabled in non-interactive Bash shells (e.g. scripts).

#!/bin/bash

trap kill_jobs 2

kill_jobs()
{
        while /usr/bin/kill 0; do :; done
        exit 0
}

foo &
foo &
foo &

sleep 777777
Myles
  • 101
  • 1
  • 4