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:

  1. [ -z $SERVICE ] checks if the variable SERVICE is empty.
  2. [ "$SERVICE" = "$i" ] checks if the variable SERVICE matches the current service name.
  3. [ "$SERVICE" = "" ] checks if the variable SERVICE 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.