Running multiple servers in a single Bash script#
I spotted this script that starts the opendream appication running both a Python uvicorn
server and a npm run start
script and it intrigued me - was it this easy to have a single Bash script run two servers? They were both started in the background with &
, like this:
1#!/bin/bash2python3 -m venv venv3source venv/bin/activate4pip install --upgrade pip5pip install -r requirements.txt6uvicorn opendream.server:app --reload &7cd webapp/opendream-ui && npm install && npm run start &8wait
But… this didn’t quite work. Hitting Ctrl+C
quit the script, but it left the two servers running in the background. I’d like the servers to stop when the wrapping Bash script is terminated.
Is there a way to do that? I asked GPT-4 and it showed me this:
1#!/bin/bash2
3# Function to be executed upon receiving SIGINT4cleanup() {5 echo "Caught SIGINT. Cleaning up..."6 kill $server_pid1 $server_pid2 # Terminates both server processes7 exit8}9
10# Set up the trap11trap cleanup SIGINT12
13# Start the Uvicorn server in the background14uvicorn opendream.server:app --reload &15server_pid1=$! # Get the process ID of the last backgrounded command16
17# Start the npm server in the background18cd webapp/opendream-ui && npm install && npm run start &19server_pid2=$! # Get the process ID of the last backgrounded command20
21# Wait indefinitely. The cleanup function will handle interruption and cleanup.22wait
And sure enough, that works perfectly!
There are a couple of tricks here, explained by ChatGPT’s comments. Each server is started using &
and then has its PID assigned to a variable using $!
to access the PID.
A cleanup()
Bash function is defined which runs kill
against both of those stored PIDs.
Then the trap
mechanism is used to cause that cleanup()
function to be triggered on SIGINT
, which is the signal that is fired when you hit Ctrl+C
:
1trap cleanup SIGINT
So now I have a pattern for any time I want to write a Bash script that starts multiple background processe and terminates them when the script itself exits.
Even better: use EXIT#
I posted this on Mastodon and got some useful tips.
You can use EXIT
instead of SIGINT
to catch other reasons that the script may execute, such as from an error.
How “Exit Traps” Can Make Your Bash Scripts Way More Robust And Reliable by Aaron Maxwell expands on this and shows some other useful tricks you can do with it, like writing an administration script that stops a service and uses an EXIT trap to be absolutely certain to start the service again when the Bash script terminates.