Linux : Bash Scripting

Scripting is a great way to both solve complex problems, and make some of the more routine tasks of maintaining a linux system less inconvenient. A Shell script is an interpreted program, written for a Shell, using the allowed functions and syntax of that shell. In our case we will be working with the Bash Shell. To start let’s create a directory for our script, and then a new file.

[user@localhost ~]$ mkdir scripting
[user@localhost ~]$ cd scripting
[user@localhost ~]$ touch test.sh
[user@localhost ~]$

We can then open the new file up in vi and immediately ad this as the first line:

#!/bin/bash

This line indicates that this script is meant to be interpreted by the Bash Shell. Next lets get the script to do something. Lets get it to output “Hello World.”

#!/bin/bash
echo "Hello World."

While this program now contains the minimum requirements to run as a Bash Script, we cannot run it yet without giving the file executable permission. We will do that with the chmod command as thus:

[user@localhost ~]$ chmod +x test.sh
[user@localhost ~]$

Once that is done we may execute the program! To execute a script from the present working directory we will need to place a ./ in front of the script name to signify the current directory. If running a script from a different directory one can simply use the path. After hitting enter the program will run as instructed.

[user@localhost ~]$ ./test.sh
Hello World.
[user@localhost ~]$

Congratulations! You have written your first shell script, and become part of a larger tradition of code learning!

Adding On

After you get over the initial excitement of echoing text to the screen, you realize your script hasn’t actually done anything except, echo text to the screen. Suppose we’d like to have our script do more than parrot a static string? Let’s expand it by having it tell us which day of the week it is.

To get this information we can use the date function. The date function can be used alone or with modifiers.

[user@localhost ~]$ date
Sat Oct 10 01:23:46 EDT 2020
[user@localhost ~]$

To display only the day of the week, we can use the %A modifier.

[user@localhost ~]$ date +%A
Saturday
[user@localhost ~]$

We can utilize the date function in our script, by assigning the output of the command to a variable, and then adding the variable to an echo statement.

#!/bin/bash
day=$(date +%A)
echo "Hello World."
echo "Today is $day."

If we now run our script, we will see the current day of the week.

[user@localhost ~]$ ./test.sh
Hello World.
Today is Saturday.
[user@localhost ~]$

User Input

We can make our script interactive, by using the read command to assign user input to a variable. In our case the variable is called name:

#!/bin/bash
day=$(date +%A)
echo "Enter your name:"
read name
echo "Hello $name."
echo "Today is $day."

If you run the script again, you will see that it pauses to allow a response to be typed in:

[user@localhost ~]$ ./test.sh
Enter your name:
_

Typing your name and hitting enter causes the script to continue its execution:

[user@localhost ~]$ ./test.sh
Enter your name:
Bentley
Hello Bentley.
Today is Saturday.
[user@localhost ~]$

Getting Practical

Suppose you are running a web development server, with a small population of virtual hosts. You could backup up your sites by issuing a zip command for each one, or you could write a script to do this as a batch process. Before we start writing a script, let’s first consider the tedious method:

[user@localhost ~]$ zip -r /var/www/backups/client.domain.com-2020-10-13-21-32.zip /var/www/client.domain.com/

Running this command will accomplish the task of creating a zip archive of the virtual host directory client.domain.com, but the command will need to be reissued for every site. Additionally both the site directory and the time stamp will need to be typed manually each time.

What our script needs to do is identify all virtual host directories, and run a zip command for each one, inserting a current time stamp into the name of each archive file, while saving to our backups location. To accomplish the first of those tasks, we can modify the ls command to display only certain folders in our /var/www directory:

[user@localhost ~]$ ls /var/www/
backups beagle.domain.com cgi-bin client.domain.com gibson.domain.com html
[user@localhost ~]$

If we use the ls command unmodified we see every directory, including our three virtual hosts. We can use a regular expression to display only directories whose name contains .domain.com and the -d flag to show those directory names themselves, rather than their file contents.

[user@localhost ~]$ ls -d /var/www/*.domain.com
/var/www/beagle.domain.com /var/www/client.domain.com /var/www/gibson.domain.com
[user@localhost ~]$

While the output from the command contains only virtual host directories, it also includes the full path, which we won’t need for naming our archive. To get a list of directory names only, we can pipe our ls output through the xargs command:

[user@localhost ~]$ ls -d /var/www/*.domain.com | xargs -n 1 basename
beagle.domain.com
client.domain.com
gibson.domain.com
[user@localhost ~]$

We can start our script by assigning the output from our modified ls command to an array:

#!/bin/sh
virtualHosts=( $(ls -d /var/www/*.domain.com | xargs -n 1 basename))

We can then use a for loop to do an action for each name in the array. For testing we will have our script print each virtual host name in order:

#!/bin/sh
virtualHosts=( $(ls -d /var/www/*.domain.com | xargs -n 1 basename))
for i in "${virtualHosts[@]}"
do
echo $i
done

If you were to run the script as it is, you would get an output identical to the output of the modified ls command directly, except that each line represents an iteration of the for loop:

[user@localhost ~]$ ./backupScript.sh
beagle.domain.com
client.domain.com
gibson.domain.com
[user@localhost ~]$

To get our time stamp we can modify the date command:

#!/bin/sh
virtualHosts=( $(ls -d /var/www/*.domain.com | xargs -n 1 basename))
for i in "${virtualHosts[@]}"
do
timeStamp=$(date +%Y-%m-%d-%H-%M)
echo $i $timeStamp
done
[user@localhost ~]$ ./backupScript.sh
beagle.domain.com 2020-10-13-22-49
client.domain.com 2020-10-13-22-49
gibson.domain.com 2020-10-13-22-49
[user@localhost ~]$

We can put it all together by placing a zip command in our for loop, referencing our variables in appropriate spaces:

#!/bin/sh
virtualHosts=( $(ls -d /var/www/*.domain.com | xargs -n 1 basename))
for i in "${virtualHosts[@]}"
do
timeStamp=$(date +%Y-%m-%d-%H-%M)
zip -r /var/www/backups/$i-$timeStamp.zip /var/www/$i
done

If we run the script now, three zip archives should appear in our backups directory:

[user@localhost ~]$ ./backupScript.sh
..
[user@localhost ~]$ ls /var/www/backups
beagle.domain.com-2020-10-13-23-01.zip client.domain.com-2020-10-13-23-01.zip gibson.domain.com-2020-10-13-23-01.zip
[user@localhost ~]$
Posted in Bash, Learn, Linux