Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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

The best that I've managed to come up with is this. It appears that the 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
share|improve this question
2  
don't cross post. serverfault.com/questions/448054 –  Patrick Nov 13 '12 at 4:29

4 Answers 4

up vote 5 down vote accepted

Your kill command is backwards.

Like many UNIX commands, options that start with a minus must come first, before other arguments.

If you write

kill -INT 0

it sees the -INT as an option, and sends SIGINT to 0 (0 is a special number meaning all processes in the current process group).

But if you write

kill 0 -INT

it sees the 0, decides there's no more options, so uses SIGTERM by default. And sends that to the current process group, the same as if you did

kill -TERM 0 -INT    

(it would also try sending SIGTERM to -INT, which would cause a syntax error, but it sends SIGTERM to 0 first, and never gets that far.)

So your main script is getting a SIGTERM before it gets to run the wait and echo DONE.

Add

trap 'echo got SIGTERM' TERM

at the top, just after

trap 'killall' INT

and run it again to prove this.

As Stephane Chazelas points out, your backgrounded children (process1, etc.) will ignore SIGINT by default.

In any case, I think sending SIGTERM would make more sense.

Finally, I'm not sure whether kill -process group is guaranteed to go to the children first. Ignoring signals while shutting down might be a good idea.

So try this:

#!/bin/bash
trap 'killall' INT

killall() {
    trap '' INT TERM     # ignore INT and TERM while shutting down
    echo "**** Shutting down... ****"     # added double quotes
    kill -TERM 0         # fixed order, send TERM not INT
    wait
    echo DONE
}

./process1 &
./process2 &
./process3 &

cat # wait forever
share|improve this answer
    
Thanks, this works! That appears to have been to problem. –  slipheed Nov 16 '12 at 17:43

Unfortunately, commands started in background are set by the shell to ignore SIGINT, and worse, they can't un-ignore it with trap. Otherwise, all you'd have to do is

(trap - INT; exec process1) &
(trap - INT; exec process2) &
trap '' INT
wait

Because process1 and process2 would get the SIGINT when you press Ctrl-C since they're part of the same process group which is the foreground process group of the terminal.

The code above will work with pdksh and zsh which in that regard are not POSIX conformant.

With other shells, you would have to use something else to restore the default handler for SIGINT like:

perl -e '$SIG{INT}=DEFAULT; exec "process1"' &

or use a different signal like SIGTERM.

share|improve this answer

If what you want is to manage some background processes, why not to use bash job control features?

$ gedit &
[1] 2581
$ emacs &
[2] 2594
$ jobs
[1]-  Running                 gedit &
[2]+  Running                 emacs &
$ jobs -p
2581
2594
$ kill -2 `jobs -p`
$ jobs
[1]-  Interrupt               gedit
[2]+  Done                    emacs
share|improve this answer

So I tinkered around with this as well. In Bash, you can define functions and do fancy things with those. My coworkers and I use terminator, so for batch executions we normally spawn a whole bunch of terminator windows if we want to view the output (less elegant than tmux tabs, but you can use a more GUI-like interface haha).

Here's the setup that I came up with, as well as an example of stuff you can run:

#!/bin/bash
custom()
{
    terun 'something cool' 'yes'
    run 'xclock'
    terun 'echoes' 'echo Hello; echo Goodbye; read'
    func()
    {
        local i=0
        while :
        do
            echo "Iter $i"
            let i+=1
            sleep 0.25
        done
    }
    export -f func
    terun 'custom func' 'func'
}

# [ Setup ]
run()
{
    "$@" &
    # Give process some time to start up so windows are sequential
    sleep 0.05
}
terun()
{
    run terminator -T "$1" -e "$2"
}
finish()
{
    procs="$(jobs -p)"
    echo "Kill: $procs"
    # Ignore process that are already dead
    kill $procs 2> /dev/null
}
trap 'finish' 2

custom

echo 'Press <Ctrl+C> to kill...'
wait

Running it

$ ./program_spawner.sh 
Press <Ctrl+C> to kill...
^CKill:  9835
9840
9844
9850
$ 

Edit @Maxim, just saw your suggestion, that makes it a ton simpler! Thanks!

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.