Simple Way to Start (and Stop!) Background Processes in Bash

I’ve often wanted a way to easily start and stop a group of processes from the command line. My most common use case is wanting to run multiple servers and/or clients at the same time, quickly starting and stopping many processes during development.

Background Processes

It’s actually quite simple to start a background process in Bash:


./someProcessA &
./someProcessB &

This will start someProcessA and someProcessB and then exit (probably before either of the processes have finished)–which works great if you just want to start the processes. But what if you want to stop them?

Ideally, I’d like my Bash script to stay running while my background processes are running. Then if I ctrl+c, my child process will get killed as well. It turns out this is actually fairly simple to do, but it took me a while to find all the right pieces.

Waiting for Background Processes to Finish

First, let’s add a wait command so that our script will wait for the background processes to finish. wait can wait on a specific PID (which you could get with something like PID=$! after spawning a background process). Or if you don’t give it any parameters, it will wait until all background processes have finished:


./someProcessA &
./someProcessB &

wait

Killing Background Processes on Script Exit

Now, our script will stay running until the background processes finish, but if we ctrl+c, our background processes will still stay running. We can fix that with a trap.


trap "kill 0" EXIT

./someProcessA &
./someProcessB &

wait

This will cause the command kill 0 to run when our script exits. The kill 0 will kill our process, and all of the processes in our process group—which contains our child processes.

Important: You’ll want to first test this in an environment where you don’t have any important unsaved work in your session or in other processes. In some circumstances, the process group can contain your X session, for example—and kill 0 will close everything you’re working on in that session.

Final Script

If you are actually using Bash, this is all you need! But other shells (such as ash or dash) won’t trigger the EXIT trap if the script is terminated by a SIGINT or SIGTERM. And ctrl+c causes a SIGINT, so to make this work on other shells, we’ll need another line:


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

./someProcessA &
./someProcessB &

wait

This adds traps for SIGINT and SIGTERM and causes the exit command to run if our script receives those signals, which in turn will trigger the EXIT trap and run kill 0, which will kill the current script and all the background processes. The ERR trap is optional. It is effectively equivalent to set -e and will cause our script to exit on error.

That’s pretty much it. This final script should clean up background processes in all the following cases:

  • We ctrl+c while running the script.
  • We kill the script in some other way externally (e.g. with kill or killall).
  • The script encounters an error.

This works quite well for my use cases. Hopefully, it’s also useful for someone else

Conversation
  • Jakob Buis says:

    Nice! I have a local utility that moves some files, clones a repo, etc. and finally adapts the vagrant configuration and reprovisions the box: reprovisioning takes much longer than the other steps.

    Your article made me realize that provision can actually be done simultaneously and I’ve adapted the script. It’s much faster now, thanks for the tip!

  • Hussein says:

    Really helpful!

  • Comments are closed.