Bash Scripting

Shell, Scripting & Bash Meaning

Shell Meaning

Shell is a macro processor which allows for interactive or non-interactive command execution. You interact with it by use of commands such as date to get the current date and time or cal to access your calendar.

Scripting Meaning

Scripting allows for an automatic command execution that would otherwise execute interactively one-by-one. You interact with it by combining the shell with a text editor. Examples of commands here include  vi to create a new file. chmod to make a file executable. Execute your new script by prefixing its name with  ./.

Use scripting to automate shell interactions.

Bash Meaning

Bash is a command language interpreter. To view your default interpreter run the command echo $SHELL

Running a command directly on the CLI:

$ echo "Hello World"
$ Hello World

Contents of helloworld.sh:

#!/bin/bash
echo "Hello World"

Change the file to be executable and then execute the file to see the output:

$ chmod +x helloworld.sh 
$ ./hello-world.sh 
Hello World

File Names and Permissions

To execute shell script the file needs to be made executable by use of chmod +x FILENAME command. 

The ls command lists all files and directories where  .sh is plausibly a shell script, and .jpg is a compressed image. On GNU/Linux systems a  file command can be used to identify a type of the file.

Relative vs Absolute Path

Using a building analogy, here is the way to help explain a relative vs absolute file path. The root directory (building’s entrance door) indicated by /  provides the entry to the entire file system (structure), hence giving access to all directories (levels/rooms) and files (people).

 To navigate to a room one on level 3, we first need to enter the central door /, then make our way to level 3  level3/ and from there enter the room1. Therefore, the absolute path to this particular room within a building is  /level3/room1. From here, if we wish to visit room2 also on level3 we first need to leave our current location that is room1 by entering ../  and then include the room’s name room2. We took a relative path to room2, which is ../room2 . We were already on level 3, so there was no need to leave the entire building and take an absolute path via main entrance /level3/room2.

There is a simpler way to navigate through the filesystem in GNU/Linux, and that is through the pwd command. This command, when executed, will always print your current location. 

Simple Backup Bash Shell Script

It is recommended not to write your entire script in one go. Slowly develop your script by testing each core line by executing it first on the terminal command line. Then transfer it to your shell script if it is successful.

Most commands accept options and arguments. Command options are used to modify the command’s behaviour to produce alternative output results and are prefixed by a ‘ -‘. Arguments are to specify the command’s execution target such as a file, directory, text etc.

Below is an example of a script used to back up our user home directory:

tar is to create the backup script. 

czf is to create a compressed tarball of the entire user home directory /home/username.

Make the script executable and run it:

Contents of backup-scripts.sh:

#!/bin/bash
tar -czf /tmp/backup.tar.gz /Users/paulgreaves/OneDrive/AttainDigital/scripts

Variables

Variables enable a programmer to store data, change and reuse them throughout a script.

New shell file using variables welcome.sh script:

#!/bin/bash
greeting="Welcome"
user=$(whoami)
day=$(date +%A)
 
echo "$greeting back $user, today is $day, which is the best day of the entire week!"
echo "Your Bash shell version is $BASH_VERSION. Enjoy!"

Below is the outcome of running the above script:

$ ./welcome.sh 
Welcome back username, today is Tuesday, which is the best day of the entire week!
Your Bash shell version is 4.4.12(1)-release. Enjoy!

Analysing the script keenly:

We have first declared a variable greeting then assigned a string value  Welcome to it. The next variable has a value of user name running a shell session. This change is through command substitution, which means that the  whoami command will be directly assigned to the user variable. The same goes for the day variable, which is produced by  date +%A command.

The echo command prints a message while substituting variable names now prefixed by  sign with their relevant values. $BASH_VERSION is an internal variable defined as part of your shell.

Note:

Never use Uppercase characters to name your private variables.

A more complex script incorporating the date and time variables of a backup while also not being tied to a specific user:

#!/bin/bash
 
# This bash script back's up a user's home directory to /tmp/.
 
user=$(whoami)
input=/home/$user
output=/tmp/${user}_home_$(date +%Y-%m-%d_%H%M%S).tar.gz
 
tar -czf $output $input
echo "Backup of $input completed! Details about the output backup file:"
ls -l $output

Below is the output of our newly updated backup script:

$ ./backup.sh 
tar: Removing leading `/' from member names
Backup of /home/userdir completed! Details about the output backup file:
-rw-r--r-- 1 userdir userdir 8778 May 16 11:30 /tmp/userdir_home_2020-05-16_113043.tar.gz

Input, Output and Error Redirections

Commands executed on GNU/Linux command line either produce output, require input or show an error message.

Both ls -l foobar commands produced an output which is displayed on your terminal by default. Both outcomes, however, are fundamentally different.

The first command tries to list non-existing file foobar which produces a standard error output ( stderr). When you generate the file by the touch command, the second execution of the  ls command produces standard output (stdout).

The difference between stdout and stderr  output is an important concept as it allows us to redirect each result separately. The > notation redirects  stdout to a file, while 2> redirects  stderr and &> redirects both  stdout and stderr. The cat  command displays the content of any given file. 

In our backup.sh script, there is an extra message displayed by tar command:

tar: Removing leading `/’ from member names

This message is telling us the absolute path has been removed; thus, extraction of the compressed file will not overwrite any existing files.

Note: stdin accepts any keystroke you type.

Below is our new backup.sh version including tar’s stderr redirection:

#!/bin/bash
 
# This bash script back's up a user's home directory to /tmp/.
 
user=$(whoami)
input=/home/$user
output=/tmp/${user}_home_$(date +%Y-%m-%d_%H%M%S).tar.gz
 
tar -czf $output $input 2> /dev/null
echo "Backup of $input completed! Details about the output backup file:"
ls -l $output

Functions

A function can a way to group several different commands into a single command, thus allowing a programmer to organise and reuse code. Functions are defined by using the function keyword and followed by function body enclosed by curly brackets.

Two functions that report many directories and files to include as part of the output compressed in the backup file.

#!/bin/bash
 
# This bash script is to back up a user's home directory to /tmp/.
 
user=$(whoami)
input=/home/$user
output=/tmp/${user}_home_$(date +%Y-%m-%d_%H%M%S).tar.gz
 
# The function total_files reports a total number of files for a given directory. 
function total_files {
       find $1 -type f | wc -l
}
 
# The function total_directories reports a total number of directories
# for a given directory. 
function total_directories {
       find $1 -type d | wc -l
}
 
tar -czf $output $input 2> /dev/null
 
echo -n "Files to be included:"
total_files $input
echo -n "Directories to be included:"
total_directories $input
 
echo "Backup of $input completed!"
 
echo "Details about the output backup file:"
ls -l $output

After going through the above backup.sh script, you will notice the following changes to the code:

There is a function called total_files. The function utilised the  find and wc commands to determine the number of files located within a directory supplied to it during the function callThere is a new function called  total_directories. It uses the same commands as total-files, but it reports several directories within a directory supplied to it during the function call

The updated script will provide a similar output to the one below:

$ ./backup.sh 
Files to be included:14
Directories to be inlcuded:2
Backup of /home/userdir completed!
Details about the output backup file:
-rw-r--r-- 1 userdir userdir 5520 May 20 08:01 /tmp/userdir_home_2020-05-20_080121.tar.gz

Numeric and String Comparisons

The below table lists rudimentary comparison operators for both numbers and strings:

DescriptionNumeric ComparisonString Comparison
Shell comparison example:[ 100 -eq 50 ]; echo $?[ “GNU” = “UNIX” ]; echo $?
less than-lt<
greater than-gt>
equal-eq=
not equal-ne!=
less or equal-leN/A
greater or equal-geN/A



There or two possible outcomes for every evaluation, true or false. If the return value is equal to 0, then the comparison evaluation is true. However, if the return value is equal to 1, the result is interpreted as false.

Using string comparison operators, we can compare strings in the same manner as when comparing numeric values.

#!/bin/bash
 
string_a="UNIX"
string_b="GNU"
 
echo "Are $string_a and $string_b strings equal?"
[ $string_a = $string_b ]
echo $?
 
num_a=100
num_b=100
 
echo "Is $num_a equal to $num_b ?"
[ $num_a -eq $num_b ]
echo $?

Conditional Statements

Conditional statements allow a programmer to implement decision making within a shell script based on certain conditions or events. The conditionals statements we are referring to are:  if, then and else.

#!/bin/bash
 
num_a=100
num_b=200
 
if [ $num_a -lt $num_b ]; then
    echo "$num_a is less than $num_b!"
fi
echo $?

After the script execution, it is important to note that, in the situation when the variable $num_a is greater than  $num_b our script fails to react. This condition is where the last piece of the puzzle, else conditional comes in handy. 

#!/bin/bash
 
num_a=400
num_b=200
 
if [ $num_a -lt $num_b ]; then
    echo "$num_a is less than $num_b!"
else
    echo "$num_a is greater than $num_b!"
fi

Line 8 now holds the else part of our conditional block. If the comparison evaluation on Line 6 reports false the code below  else statement, in our case, Line 9 executes.

 Note: 

When counting lines in a bash script, even ones without code or statements are counted as shown above.

Positional Parameters

Positional parameters are assigned via command-line arguments and are accessible within a script as $1, $2…$N variables. During the script execution, any additional items supplied after the program name are considered arguments and are available during the script execution.

Simple arguments example:

#!/bin/bash
 
echo $1 $2 $4
echo $#
echo $*

On line 3, we print 1st, 2nd and 4th positional parameters exactly in the order as they are supplied during the script’s execution. The 3rd parameter is available but deliberately omitted on this line. Using  $# on Line 4, we are printing the total number of supplied arguments. This total number is useful when we need to check how many arguments the user provided during the script execution. Lastly, the  $* on Line 5, is used to print all arguments.

Updating our backup.sh script for it to accept arguments:

#!/bin/bash
# This bash script back's up a user's home directory to /tmp/.
 
if [ -z $1 ]; then
    user=$(whoami)
else
    if [ ! -d "/home/$1" ]; then
        echo "Requested $1 user home directory doesn't exist."
        exit 1
    fi
    user=$1
fi
 
input=/home/$user
output=/tmp/${user}_home_$(date +%Y-%m-%d_%H%M%S).tar.gz
 
function total_files {
    find $1 -type f | wc -l
}
 
function total_directories {
    find $1 -type d | wc -l
}
 
function total_archived_directories {
    tar -tzf $1 | grep  /$ | wc -l
}
 
function total_archived_files {
    tar -tzf $1 | grep -v /$ | wc -l
}
 
tar -czf $output $input 2> /dev/null
 
src_files=$( total_files $input )
src_directories=$( total_directories $input )
 
arch_files=$( total_archived_files $output )
arch_directories=$( total_archived_directories $output )
 
echo "Files to be included: $src_files"
echo "Directories to be included: $src_directories"
echo "Files archived: $arch_files"
echo "Directories archived: $arch_directories"
 
if [ $src_files -eq $arch_files ]; then
        echo "Backup of $input completed!"
        
echo "Details about the output backup file:"
        ls -l $output
else
    echo "Backup of $input failed!"
fi

Results of the functions example in Bash:

$ ./backup.sh 
Files to be included: 24
Directories to be included: 4
Files archived: 24
Directories archived: 4
Backup of /home/userdir completed!
Details about the output backup file:
-rw-r--r-- 1 userdir userdir 675709 Sep 14 11:45 /tmp/userdir_home_2020-05-20_084521.tar.gz
 
$ ./backup.sh abc123
Requested abc123 user home directory doesn't exist.
 
$ ./backup.sh paulgreaves
Files to be included: 5
Directories to be included: 3
Files archived: 5
Directories archived: 3
Bash Scripting
Shell, Scripting & Bash Meaning Shell Meaning Shell is a macro processor […]
Red Hat Ansible Automation
If you need to manage your firewalls using ansible, below, you will […]
Rectenna - create power through wifi
“Rectenna” now that is a name to remember. Scientists are raising the […]