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.
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 "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.
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
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
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
- The script encounters an error.
This works quite well for my use cases. Hopefully, it’s also useful for someone else
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!
Comments are closed.