How do I kill background processes / jobs when my shell script exits?

I am looking for a way to clean up the mess when my top-level script exits.

Especially if I want to use set -e, I wish the background process would die when the script exits.


Solution 1:

To clean up some mess, trap can be used. It can provide a list of stuff executed when a specific signal arrives:

trap "echo hello" SIGINT

but can also be used to execute something if the shell exits:

trap "killall background" EXIT

It's a builtin, so help trap will give you information (works with bash). If you only want to kill background jobs, you can do

trap 'kill $(jobs -p)' EXIT

Watch out to use single ', to prevent the shell from substituting the $() immediately.

Solution 2:

This works for me (improved thanks to the commenters):

trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
  • kill -- -$$ sends a SIGTERM to the whole process group, thus killing also descendants.

  • Specifying signal EXIT is useful when using set -e (more details here).

Solution 3:

Update: https://stackoverflow.com/a/53714583/302079 improves this by adding exit status and a cleanup function.

trap "exit" INT TERM
trap "kill 0" EXIT

Why convert INT and TERM to exit? Because both should trigger the kill 0 without entering an infinite loop.

Why trigger kill 0 on EXIT? Because normal script exits should trigger kill 0, too.

Why kill 0? Because nested subshells need to be killed as well. This will take down the whole process tree.

Solution 4:

trap 'kill $(jobs -p)' EXIT

I would make only minor changes to Johannes' answer and use jobs -pr to limit the kill to running processes and add a few more signals to the list:

trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT