Optimizing Multiple Services: A Comprehensive Bash Script for Running and Monitoring
Introduction
In this blog article, we will explore and explain a bash script that allows you to run and monitor multiple services simultaneously. The script utilizes various Bash features and commands to achieve this functionality. By understanding the code, you’ll be able to customize it for your specific use case or gain insights into shell scripting.
Script Overview
The script is written in Bash and starts by declaring the interpreter and enabling certain options. It then initializes variables and sets up a named pipe to capture service logs. Next, it defines a function to handle the script’s exit and registers a trap for the interrupt signal (SIGINT).
The script utilizes an array to specify the names of services to be executed. It iterates through each service, enters the respective directory, and launches the service using the go run
command. The standard error (2) is redirected to the named pipe, and standard output (1) is discarded. The script also captures the process IDs (PIDs) of the running services for later termination if needed.
Finally, the script uses the tail
command to continuously monitor the named pipe, displaying the logs in real-time. If the script receives an interrupt signal (e.g., Ctrl+C), it invokes the exit function, which terminates all the running services and exits the script.
Let’s dive deeper into the code
#!/usr/bin/env bash
set -ex
This shebang line specifies the interpreter to use (in this case, bash
). The set -ex
command enables two options: -e
causes the script to exit immediately if any command fails, and -x
prints each command before executing it for better visibility during debugging.
PWD=$(pwd)
mkfifo /tmp/servicelogs.pipe || echo "pipe already exists"
runningPIDs=()
The PWD=$(pwd)
line assigns the current working directory to the PWD
variable. The mkfifo
command creates a named pipe called /tmp/servicelogs.pipe
if it doesn’t already exist. The runningPIDs
variable is an empty array that will store the PIDs of the running services.
exitfn () {
#trap SIGINT # Restore signal handling for SIGINT
echo; echo 'Stopping services'
for theId in ${runningPIDs[@]}; do
echo "killing ${theId}"
kill "$theId" || echo "no such process maybe"
done
exit
}
trap "exitfn" INT
The exitfn
function is defined to handle the script’s termination. It prints a message indicating the services are being stopped. Then, it iterates through each PID in the runningPIDs
array and attempts to terminate the associated process using the kill
command. If a process does not exist, it displays an appropriate message. Finally, the exit
command terminates the script.
The trap "exitfn" INT
line registers the exitfn
function to be called when an interrupt signal (SIGINT) is received. This ensures that the services are properly stopped before the script exits.
SERVICE_PREFIX="service-"
declare -a arr=("multiple" "folders" "to" "watch" "logs" "from")
for i in "${arr[@]}"
do
if [ -z $SERVICE ] || [ "$SERVICE" = "$i" ] || [ "$SERVICE" = "" ]; then
cd ../${SERVICE_PREFIX}$i
go run "./cmd/$i/main.go" 2>&1 > /tmp/servicelogs.pipe &
echo "$i started with pid $!"
runningPIDs+=($!)
fi
done
The script sets the SERVICE_PREFIX
variable to “service-“ and initializes an array called arr
with the names of the services. It then iterates through each element in arr
.
Inside the loop, it checks three conditions using the [
command:
[ -z $SERVICE ]
checks if the variableSERVICE
is empty.[ "$SERVICE" = "$i" ]
checks if the variableSERVICE
matches the current service name.[ "$SERVICE" = "" ]
checks if the variableSERVICE
is explicitly empty.
If any of the conditions are true, the script changes the directory to “../service-i” (where i is the current service name). It then executes the go run
command to run the main Go file of the service, redirecting the standard error (2) to the named pipe /tmp/servicelogs.pipe
. The standard output (1) is discarded. The &
symbol runs the command in the background. The script also prints a message indicating that the service has started, along with its PID. Finally, it adds the PID to the runningPIDs
array for later termination.
tail -f /tmp/servicelogs.pipe
The tail -f
command continuously reads the contents of the named pipe /tmp/servicelogs.pipe
and outputs them to the console in real-time. This allows you to monitor the logs of all the running services simultaneously.
Conclusion
This bash script provides a convenient way to run and monitor multiple services simultaneously. By understanding its various components and the purpose of each command, you can adapt it to your own use cases or enhance it further. Shell scripting allows for automation and streamlining of various tasks, making it a valuable skill for developers and system administrators.