Intro to Bash Scripting.

As always, use anything here at your own risk. I just had to go through writing up a script and decided to make some notes for myself.

What are Shell Scripts?

Shell scripts are a collection of CLI (Command Line Interface) commands that are saved to a text file. They are used to automate some function or task. On Linux systems, shell scripts will often be written in Bash (Bourne Again Shell), but they can be written in a variety of different shell languages. Really they can be written in any language that the computer you're working on has a CLI interpreter installed. Other shells, such as Fish, or even Python or Perl.

Creating a Script

Creating a shell script is quite easy. As mentioned it's just s text file with a shebang. You can create a new text file in your favorite text editor or via your file manager. Or you could create one with the CLI by entering touch scriptname.sh, and the file will be created in your current working directory. The .sh isn't mandatory, but it's a good naming practice to easily identify shell script files.

At this poing you simply have a blank text file. The shebang is what actually makes it a shell script file, well, sort of. Technically you can omit the shebang as well, but you'll have to call the correct interpreter when you call to exectue the script. So it's really a good practice to include the shebang on all script files to help identify the programming language used in the script, and to be able to just call the script without also specifying the interpreter every time you call it.

The shebang is the opening line of the file that tells the computer which shell, or interpreter to use. Here are a few examples of what a shebang looks like.

# for bash, your first line should be
#!/bin/bash

# for a fish shell script
#!/usr/bin/fish

# for a python script, it might look like this
#!/usr/bin/env python3

Note the #! characters. Those characters make this line the 'shebang' statement. What follows is the path to the appropriate interpreter for the commands that follow. The first two examples are direct paths to the shell binary. If you're not sure what the full path is to your shell binary, you can use which bash or which fish to obtain the full path. Alternatively, the python example above is a good way to format your shebang if your script will be shared with other computers which may not have the exact same path. This method calls env, which will parse through the $PATH environmental variable (which is a list of all the computers configured binary paths) and looks for your binary among those directories.

At this point you have everything you need to finish writing your first script. Let's add a Hello World statment to our script.

#!/usr/bin/env bash

echo "Hello World!"

Now before you can actually run the script, you have to tell the computer that this file is an executable. When you first create a text file, with any of the methods mentioned ealier, it will not be executable by default.

Here is how you can check the file settings, make it executable, and verify the change.

ls -lh
-rw-rw-r-- 1 username groupname 0 May 26 11:17 myscript.sh

chmod +x myscript.sh

ls -lh
-rwxrwxr-x 1 username groupname 0 May 26 11:17 myscript.sh

As you can see, the script is now marked executable by any user, group, and even anonmyous. In most cases, this is probably not what you want to leave it as. Be sure to fine tune your permissions on the script file as needed for what you are trying to accomplish.

At this point you should be able to successfully execute your script.

./myscript.sh
Hello World!

A More Advanced Script Example

Here is an example of a more advanced script that monitors a systemd process, attempts to restart it if it's not active, and then emails the administrator if it cannot be restarted. In order to have the script send an email, you'll need to have something like Postfix or Sendmail installed and configured on your machine, that's beyond the scope of this writeup.

In the following script note the use of comments, variables, functions, while loop, if-else statement, etc.

#!/usr/bin/env bash

# Set a variable for the service to be monitored
SERVICE=nginx.service

# Set variables for admin email and server name
EMAIL=serveradmin@mydomain.com
SERVER=hostnameofserver

# Create a function to check if the service is active
check_active() {
  systemctl is-active --quiet $SERVICE
}

# Create a function to start the service
start_service() {
  systemctl start $SERVICE
}

# Now we set a counter and use a While loop to
# attempt to restart the service 3 times before
# emailing the administrator

COUNTER=1

while !check_active; do

  if [ $COUNTER -le 3 ];

    start_service

  else

    mail -s "$SERVICE is down" $EMAIL <<< "$SERVICE is down on $SERVER and has failed to start after 3 attempts."

    break

  fi

  sleep 10

  ((COUNTER++))

done

Now since this will be an automated script running as a cron job, we'll want to make sure that it is properly protected. I am going to do the following, your situation might be a little different.

# move the script to the root home dir
sudo mv scriptname.sh /root/scriptname.sh

# setting the permissions to 750 will make the file
# executable and readable by root only and noone else
sudo chmod 750 /root/scriptname.sh

Next we'll need to schedule a cron job to automatically run this script on some frequency. If we want to check that the service is running every five minutes, create a cron job as follows.

# to add/edit root crontab jobs
sudo crontab -e

# to run this script every five minutes
*/5 * * * * /root/scriptname.sh

Monitoring Multiple Services

Can we make a small modification to this script to make it capable of checking multiple services so we don't have to create a separate script for each service? I think we probably can.

I think we have to do two things:

  1. Make the $SERVICES variable an array of service names.
  2. Wrap the while loop in a for loop that iterates through each of the services listed in the array variable.

After making these changes, the final script should look like this.

#!/usr/bin/env bash

# Set a variable for the service to be monitored
SERVICES=(nginx.service mattermost.service)

# Set variables for admin email and server name
EMAIL=serveradmin@mydomain.com
SERVER=hostnameofserver

# Create a function to check if the service is active
check_active() {
  systemctl is-active --quiet $SERVICE
}

# Create a function to start the service
start_service() {
  systemctl start $SERVICE
}

for SERVICE in $SERVICES; do

  # Now we set a counter and use a While loop to
  # attempt to restart the service 3 times before
  # emailing the administrator
  
  COUNTER=1
  
  while !check_active; do
  
    if [ $COUNTER -le 3 ];
  
      start_service
  
    else
  
      mail -s "$SERVICE is down" $EMAIL <<< "$SERVICE is down on $SERVER and has failed to start after 3 attempts."
  
      break
  
    fi
  
    sleep 10
  
    ((COUNTER++))
  
  done

done

Closing

One thing I think I need to figure out yet is if a service fails and I am unable to attend to it quickly, I'll recieve a new email every five minutes. I think it would be good to have a way to detect that situation and prevent repeat emails. Otherwise I think this is a good little intro into bash scripting and creating a useful script if you have a need to monitor some services on a server.

  1. Previous Post
    ssh-askpass Frustration
    Thu, Dec 28, 2023
    Troubleshooting ssh-askpass Arch Linux Wayland
    Some notes on what finally worked for me when I ran into trouble with ssh-askpass.