Chapter 6
Automating Tasks with Scripts

 6.1 Prerequisites
  6.1.1 Assumed Knowledge
  6.1.2 Linux and Network Setup
 6.2 Introduction to Scripts
  6.2.1 Shell Scripts are Text Files
  6.2.2 Variables in Scripts
  6.2.3 For Loops
  6.2.4 If/Then/Else
  6.2.5 Input Parameters
  6.2.6 Executing Shell Scripts
 6.3 More Scripting Examples
  6.3.1 First Script with echo and ls
  6.3.2 Using Variables
  6.3.3 For Loops
  6.3.4 If/Then/Else
  6.3.5 Input Arguments
  6.3.6 Reading a Text File
  6.3.7 Extra Commands

File: nsl/scripts.tex, r1670

This chapter introduces you to writing scripts that automate tasks with the Linux the command line. While you do not need to write scripts to complete tasks in most of the remaining chapters, being able to create simple scripts will simplify tasks when frequently using the command line. You may skip this chapter and return to it if (when?) you say to yourself: “I am running the same commands over and over—isn’t there a more efficient way to do this?”.

6.1 Prerequisites

6.1.1 Assumed Knowledge

This chapter assumes you have knowledge of:

6.1.2 Linux and Network Setup

All of the practical tasks in this chapter can be completed on a single Linux computer. Although virtnet (Chapter 3) is not required, if you do use it, as only a single computer is necessary, topology 1 is appropriate (or in fact any topology—just use a single node).

6.2 Introduction to Scripts

The shell is the software that interprets the commands you type in on a terminal. It is a program itself, and there are many different implementations: sh (the original), Bash, Csh, Tcsh, Zsh, Dash, Ksh, … . Bash is very common today and is the default on Ubuntu Linux and Mac OSX, and therefore we will focus on that.

The shell defines how you interact with the operating system on the terminal. The most common interaction is simply typing the name of an application or command, followed by optional parameters. The shell then executes that application. However a shell has much more, including features that allow you to combine multiple commands to complete more complex tasks than what a single application can do on its own. For convenience, rather than typing a set of commands on the terminal, they are usually included in a file, and then the shell executes that file. Such a file is called a shell script.

This chapter is a very quick introduction to shell scripting, first covering the concepts with short examples, and then presenting some longer examples in Section 6.3. There are many sources that explain shell scripting, including:

6.2.1 Shell Scripts are Text Files

Let’s create a first shell script. Use any text editor (e.g. nano, vi, emacs) to create a file containing your commands. Below is an example with the file called script-example1.sh contain just two lines.

$ cat script-example1.sh
#!/bin/bash
ls -l ~/

The script script-example1.sh is just a text file with two lines. The first line is a special line that indicates to the shell what interpreter (shell) should be used to execute the following commands. Although it is not necessary, it is good practice to include such a line. Note that later we will see everything after a # (hash) is a comment; however this is a special case where the first two characters of the file are #! (shebang), which means its not actually a comment.

The 2nd line of script-example1.sh is the only command to execute in this script: list in long format the files in my home directory.

You can execute the script by passing its name as a parameter to bash. As a result the commands inside the file are executed.

$ bash script-example1.sh
total 12
-rw-rw-r-- 1 network network  174 Mar  2  2017 lynx.cfg
-rw-rw-r-- 1 network network   21 Jan 14 17:44 script-example1.sh
drwxrwxr-x 6 network network 4096 Feb 10  2017 virtnet

Note that the output from the above demo may differ from your computer (as you may have different files).

6.2.2 Variables in Scripts

Variables can be used in shell scripts as demonstrated in script-example2.sh. You refer to the value by preceding the variable name with a $ (dollar sign). Optionally, you may enclose the variable name in {} (braces). Everything after a # (hash) is a comment and is not executed.

$ cat script-example2.sh
                                                                                      
                                                                                      
#!/bin/bash
myname="Steven Gordon"
# Variable names can be enclosed in braces { }
echo ${myname}
echo "My name is $myname" # or optionally the braces can be omitted
# It is good practice to include the braces
$ bash script-example2.sh
Steven Gordon
My name is Steven Gordon

6.2.3 For Loops

For loops can loop across numbers, using C-like syntax, as well as loop across lists, including lines in a file. Some examples:

$ cat script-data1.txt
123,456,abc
789,012,def
345,678,ghi
$ cat script-example3.sh
#!/bin/bash
for ((i=1; i<=3; i++));
do
     echo $i
done

for name in Steve John Lily;
do
     echo ${name}
done

for line in `cat script-data1.txt`;
do
     echo ${line} | cut -d "," -f 2
done
$ bash script-example3.sh
1
2
3
Steve
John
Lily
456
012
                                                                                      
                                                                                      
678

6.2.4 If/Then/Else

Conditional statements are possible using if/then/else style. The hardest part is the testing of conditions. This is normally done using the test command, which has a short form of enclosing the conditional statement in [] (square brackets). See man test to see the syntax for different conditions.

$ cat script-example4.sh
#!/bin/bash
cutoff=2
for ((i=1; i<=3; i++));
do
     if [ $i -lt $cutoff ];
     then
         echo "$i is less than $cutoff"
     elif [ $i -eq $cutoff ];
     then
         echo "$i is is equal to $cutoff"
     else
         echo "$i is not less than $cutoff"
     fi
done

for name in Steve John Lily;
do
     if [ "$name" = "Lily" ];
     then
         echo "$name is the boss"
     fi
done

filename="script-data1.txt";
if [ -e ${filename} ];
then
     echo "${filename} exists"
fi
$ bash script-example4.sh
1 is less than 2
2 is is equal to 2
3 is not less than 2
Lily is the boss
script-data1.txt exists
                                                                                      
                                                                                      

6.2.5 Input Parameters

A script can take input arguments/parameters, in the same way most commands do. These are called positional parameters and referred to using a number of the position listed on the command line, e.g. $1 is the first parameter, $2 is the second parameter, …

$ cat script-example5.sh
#!/bin/bash
ls -l $1 | grep $2
$ bash script-example5.sh /usr/bin sum
-rwxr-xr-x 1 root   root      30460 Feb 18  2016 cksum
-rwxr-xr-x 1 root   root    3956356 Jan 19  2017 innochecksum
-rwxr-xr-x 1 root   root      42780 Feb 18  2016 md5sum
lrwxrwxrwx 1 root   root          6 Feb 10  2017 md5sum.textutils -> md5sum
-rwxr-xr-x 1 root   root      42780 Feb 18  2016 sha1sum
-rwxr-xr-x 1 root   root      50972 Feb 18  2016 sha224sum
-rwxr-xr-x 1 root   root      50972 Feb 18  2016 sha256sum
-rwxr-xr-x 1 root   root      87836 Feb 18  2016 sha384sum
-rwxr-xr-x 1 root   root      87836 Feb 18  2016 sha512sum
-rwxr-xr-x 1 root   root       9332 Mar 13  2016 shasum
-rwxr-xr-x 1 root   root      42784 Feb 18  2016 sum

6.2.6 Executing Shell Scripts

So far we have executed the shell scripts by passing the file name as a parameter to bash. Another way is to make the script file executable (Chapter 7 explains the chmod command and permissions):

$ chmod u+x script-example1.sh

And now you can run the script like other programs:

 ./script-example1.sh
total 32
-rw-rw-r-- 1 network network  174 Mar  2  2017 lynx.cfg
-rw-rw-r-- 1 network network   36 Jan 14 17:51 script-data1.txt
-rwxrw-r-- 1 network network   21 Jan 14 17:44 script-example1.sh
-rw-rw-r-- 1 network network  209 Jan 14 17:50 script-example2.sh
                                                                                      
                                                                                      
-rw-rw-r-- 1 network network  191 Jan 14 17:53 script-example3.sh
-rw-rw-r-- 1 network network  473 Jan 14 17:55 script-example4.sh
-rw-rw-r-- 1 network network   31 Jan 14 17:58 script-example5.sh
drwxrwxr-x 6 network network 4096 Feb 10  2017 virtnet

But we need to include “./” in front of the name to tell the shell that the command/program script-example1.sh can be found in “this” directory. If you want to avoid including “./” then the directory that stores the script must by in the PATH environment variable. Let’s say we create a directory that contains all our scripts (/home/network/scripts) and move them into that directory (This is just an example; it is probably better to use the directory /home/network/bin to store your scripts and applications). Let’s also make them executable.

$ mkdir scripts
$ mv script-example*.sh scripts/
$ chmod u+x scripts/*
$ ls -l scripts/
total 20
-rwxrw-r-- 1 network network  21 Jan 14 17:44 script-example1.sh
-rwxrw-r-- 1 network network 209 Jan 14 17:50 script-example2.sh
-rwxrw-r-- 1 network network 191 Jan 14 17:53 script-example3.sh
-rwxrw-r-- 1 network network 473 Jan 14 17:55 script-example4.sh
-rwxrw-r-- 1 network network  31 Jan 14 17:58 script-example5.sh

Now lets add our directory to the PATH environment variable. First we show the current PATH, and then add our directory to it:

$ echo $PATH
/home/network/bin:/home/network/.local/bin:/usr/local/sbin:/usr/local/bin:/usr
/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/network/virtnet/bin
$ PATH=/home/network/scripts:$PATH
$ echo $PATH
/home/network/scripts:/home/network/bin:/home/network/.local/bin:/usr/local/sbin
:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/ho
me/network/virtnet/bin

Now we can execute our scripts from any directory by just typing the name.

$ script-example1.sh
total 16
-rw-rw-r-- 1 network network  174 Mar  2  2017 lynx.cfg
-rw-rw-r-- 1 network network   36 Jan 14 17:51 script-data1.txt
drwxrwxr-x 2 network network 4096 Jan 14 19:02 scripts
drwxrwxr-x 6 network network 4096 Feb 10  2017 virtnet

But be careful: some of the example scripts above referred to relative files (e.g. data1.txt), so may longer work. Try to fix them.

6.3 More Scripting Examples

The previous section introduced some basic concepts of scripting, with short examples. Here we present several more example scripts. You may use them, along with other online resources, to learn basics of shell scripts. You should run the script, and then inspect the source code, which include comments, to understand the output produced.

The following sections show the example scripts. You can also download the individual files or a zip or tgz archive containing all the source files.

While example output of the scripts is shown, note that the output on your computer may differ (e.g. different set of files, different times).

6.3.1 First Script with echo and ls

A very simple script that shows how to get started using echo and ls.

 
#!/bin/bash 
# Lines starting with a hash (#) are comments EXCEPT the first line above. 
# The first line tells us what shell to use to run this script. You should 
# include it in every script file. 
 
# Scripts contain commands as you would execute on the command line 
echo "Listingallfilesinreversetimeorder" 
 
ls -lrta 
 
echo "Endoflisting"

Example output after running this script is below.

$ bash demo_start.sh
Listing all files in reverse time order
total 80
drwxr-xr-x 3 root    root    4096 Feb 10  2017 ..
-rw-r--r-- 1 network network 3771 Feb 10  2017 .bashrc
-rw-r--r-- 1 network network  655 Feb 10  2017 .profile
-rw-r--r-- 1 network network  220 Feb 10  2017 .bash_logout
-rw-r--r-- 1 network network    0 Feb 10  2017 .sudo_as_admin_successful
drwx------ 3 network network 4096 Feb 10  2017 .cache
drwx------ 3 network network 4096 Feb 10  2017 .local
drwxrwxr-x 3 network network 4096 Feb 10  2017 .subversion
drwxrwxr-x 6 network network 4096 Feb 10  2017 virtnet
drwxrwxr-x 2 network network 4096 Feb 10  2017 .ssh
lrwxrwxrwx 1 network network   62 Feb 10  2017 .bash_aliases -> /home/network/virtnet/data/defaults/home/network/.bash_aliases
-rw-rw-r-- 1 network network  174 Mar  2  2017 lynx.cfg
-rw------- 1 network network   47 Mar  2  2017 .bash_history
-rw-r--r-- 1 network network  327 Jan 15 13:06 demo_arguments.sh
-rw-r--r-- 1 network network  357 Jan 15 13:06 demo_for.sh
-rw-r--r-- 1 network network  555 Jan 15 13:06 demo_extras.sh
-rw-r--r-- 1 network network 1010 Jan 15 13:06 demo_readfile.sh
-rw-r--r-- 1 network network  906 Jan 15 13:06 demo_if.sh
-rw-r--r-- 1 network network  347 Jan 15 13:06 demo_start.sh
drwxr-xr-x 7 network network 4096 Jan 15 13:06 .
-rw-r--r-- 1 network network  846 Jan 15 13:06 demo_variable.sh
End of listing

6.3.2 Using Variables

This script shows several examples creating and using variables.

 
#!/bin/bash 
# Demo of variables 
 
echo "HelloWorld" 
 
# Variables are assigned values using the equal (=) sign 
a="Hello" 
 
# Values are accessed using the dollar ($) sign followed by the variable name 
# It is good practice to include the variable name inside braces ({}) to avoid 
# confusion with non-variables. 
echo "${a}" 
 
b="World" 
echo "${a}${b}!" 
 
# Variables can be used almost anywhere 
directory="/etc" 
ls ${directory} 
 
# Variables can be set to the result of executing a command by enclosing 
# that command in back ticks/quotes (‘‘) 
c=‘echo "${a}${b}!" 
 
echo "Theansweris:${c}" 
 
# A different way ... 
d=$(ls /var | tail -3) 
echo "${d}" 
 
 
# Normally we are not concerned with data types, although there 
# is a difference between integers and strings. 
x=1 
y=2 
 
# Simple mathematical expressions are possible 
echo $(( $x + $y * $y ))

Example output after running this script is below.

$ bash demo_variable.sh
                                                                                      
                                                                                      
Hello World
Hello
Hello World!
acpi      gss     mdadm       resolv.conf
adduser.conf    host.conf   mime.types     rmt
alternatives    hostname   mke2fs.conf     rpc
...
gshadow      mailcap.order   rcS.d       xdg
gshadow-    manpath.config   resolvconf     xml
The answer is: Hello World!
spool
tmp
www
5

6.3.3 For Loops

This script demonstrates different approaches for creating for control loops.

 
#!/bin/bash 
# Demo - for loops 
 
# There are different ways to create for loops 
 
echo "Example1" 
for i in 1 2 3 4; 
do 
      echo ${i} 
done 
 
echo "Example2" 
i=1 
for j in ls /var‘; 
do 
      echo "${i}:${j}" 
      i=$(( ${i} + 1 )); 
done 
 
echo "Example3" 
for x in {1..10}; 
do 
      echo ${x} 
done 
 
echo "Example4" 
LIMIT=5 
for ((i=0; i<${LIMIT}; i++)); 
do 
      echo "${i}" 
done

Example output after running this script is below.

$ bash demo_for.sh
Example 1
1
2
3
4
Example 2
1: backups
2: cache
3: crash
4: lib
5: local
6: lock
7: log
8: mail
9: opt
10: run
                                                                                      
                                                                                      
11: spool
12: tmp
13: www
Example 3
1
2
3
4
5
6
7
8
9
10
Example 4
0
1
2
3
4

6.3.4 If/Then/Else

This script demonstrates if-then-else statements using test.

 
#!/bin/bash 
# Demo: if statements 
 
# if statements test if conditions are true 
 
# Read the manual for "test" to see syntax of conditions 
 
echo "Testingstrings..." 
a="Hello" 
if [ ${a} = "Hello" ]; 
then 
      echo "aisHello" 
else 
      echo "aisnotHello" 
fi 
 
echo -e "\nTestingexistenceoffiles..." 
filename="/home/network/.bash_history" 
if [ -f ${filename} ]; 
then 
      echo "${filename}existsandisregularfile." 
else 
      echo "${filename}doesntexist(orisnotaregularfile)." 
fi 
 
filename="/etc" 
if [ ! -d ${filename} ]; 
then 
      echo "${filename}isnotadirectory" 
else 
      echo "${filename}isadirectory" 
fi 
 
 
 
echo -e "\nTestingintegers..." 
for b in {1..7}; 
do 
      if [ ${b} -le 2 ]; 
      then 
            echo "${b}islessthanorequalto2" 
      else 
            if [ ${b} -gt 5 ]; 
            then 
                  echo "${b}isgreaterthan5" 
            elif [ ${b} -eq 3 ]; 
            then 
                  echo "${b}isequalto3" 
            else 
                  echo "${b}is4or5" 
            fi 
      fi 
done

Example output after running this script is below.

$ bash demo_if.sh
Testing strings ...
a is Hello

Testing existence of files ...
/home/network/.bash_history exists and is regular file.
/etc is a directory

Testing integers ...
1 is less than or equal to 2
2 is less than or equal to 2
                                                                                      
                                                                                      
3 is equal to 3
4 is 4 or 5
5 is 4 or 5
6 is greater than 5
7 is greater than 5

6.3.5 Input Arguments

A demonstration of reading and using command line arguments to your script.

 
#!/bin/bash 
# Demo - command line arguments 
 
# Arguments or parameters to the script are referenced by $1, $2, $3, ... 
 
# If the first command line argument is non-zero length 
if [ -n "$1" ]; 
then 
      echo "Argument1:$1" 
      if [ -n "$2" ]; 
      then 
            echo "Argument2:$2" 
            if [ -n "$3" ]; 
            then 
                  echo "Argument3:$3" 
            fi 
      fi 
fi

Example output after running this script is below.

$ bash demo_arguments.sh one
Argument1: one
$ bash demo_arguments.sh one two
Argument1: one
Argument2: two
$ bash demo_arguments.sh hello world bye
Argument1: hello
Argument2: world
Argument3: bye
$ bash demo_arguments.sh one two three four
Argument1: one
Argument2: two
Argument3: three

6.3.6 Reading a Text File

This script creates a temporary text file with content, and then reads that file in line by line.

 
#!/bin/bash 
# Demo - read file 
 
# Lets first create a text file 
 
# We will use mktemp to make a temporary file 
examplefile=‘mktemp 
echo "Thetemporaryfileis:${examplefile}" 
 
# Now lets put some example text in our file 
 
# First line 
echo "Thisisthefirstline" >> ${examplefile} 
 
# Second line 
echo "Andthesecondline" >> ${examplefile} 
 
# This is a bit slow. Can we write multiple lines at once? Yes ... 
cat >> ${examplefile} <<End-of-text 
Third line 
The 4th line 
The 5th line 
And some more lines 
And more 
That is enough 
End-of-text 
 
# In the above "cat" everything between "End-of-text" is "cat" into our file 
 
# Lets check our file by showing it 
echo "===============" 
cat ${examplefile} 
echo "===============" 
 
# Now lets read the file in, line by line 
i=1 
while read line 
do 
      echo "Line${i}:${line}" 
      i=$(( $i + 1 )) 
done < ${examplefile} 
 
 
# Now cleanup ... 
# If we make a temporary file it is usually a good idea to delete it at the end 
if [ -f ${examplefile} ]; 
then 
      rm -f ${examplefile} 
fi

Example output after running this script is below.

$ bash demo_readfile.sh
The temporary file is: /tmp/tmp.dpuWG5rrZs
===============
This is the first line
And the second line
Third line
The 4th line
The 5th line
And some more lines
And more
That is enough
===============
Line 1: This is the first line
Line 2: And the second line
Line 3: Third line
Line 4: The 4th line
Line 5: The 5th line
Line 6: And some more lines
Line 7: And more
Line 8: That is enough

6.3.7 Extra Commands

A final demonstration of few different tools/commands.

 
#!/bin/bash 
# Demo - some extra things 
 
echo "tr" 
echo "Thisisasentence." | tr   _ 
echo "Thisisanothersentence." | tr -d e 
echo "" 
 
echo "basename" 
basename /etc/pam.conf 
basename /etc/pam.conf .conf 
echo "" 
 
echo "date" 
date 
date +’%Y-%m-%d-%H-%M 
echo "" 
 
echo "file" 
file $0 
echo "" 
 
echo "stat" 
stat $0 
echo "" 
 
echo "mktemp" 
tmpfile=‘mktemp 
echo ${tmpfile} 
echo "hello" > ${tmpfile} 
ls -l ${tmpfile} 
rm -f ${tmpfile} 
echo "" 
 
echo "tar" 
tar cf test_script_archive1.tar /etc/apt/ 
tar czf test_script_archive2.tgz /etc/apt/ 
echo ""

Example output after running this script is below.

$ bash demo_extras.sh
tr
This_is_a_sentence.
This is anothr sntnc.

basename
pam.conf
                                                                                      
                                                                                      
pam

date
Tuesday 15 January  13:21:11 AEST 2019
2019-01-15-13-21

file
demo_extras.sh: Bourne-Again shell script, ASCII text executable

stat
  File: 'demo_extras.sh'
  Size: 555         Blocks: 8          IO Block: 4096   regular file
Device: 801h/2049d  Inode: 129309      Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/ network)   Gid: ( 1000/ network)
Access: 2019-01-15 13:21:10.996000000 +1000
Modify: 2019-01-15 13:06:30.564000000 +1000
Change: 2019-01-15 13:06:30.564000000 +1000
 Birth: -

mktemp
/tmp/tmp.zpmWOJl8Nr
-rw------- 1 network network 6 Jan 15 13:21 /tmp/tmp.zpmWOJl8Nr

tar
tar: Removing leading `/' from member names
tar: Removing leading `/' from member names

$ ls -l
total 92
-rw-r--r-- 1 network network   327 Jan 15 13:06 demo_arguments.sh
-rw-r--r-- 1 network network   555 Jan 15 13:06 demo_extras.sh
-rw-r--r-- 1 network network   357 Jan 15 13:06 demo_for.sh
-rw-r--r-- 1 network network   906 Jan 15 13:11 demo_if.sh
-rw-r--r-- 1 network network  1010 Jan 15 13:06 demo_readfile.sh
-rw-r--r-- 1 network network   347 Jan 15 13:06 demo_start.sh
-rw-r--r-- 1 network network   846 Jan 15 13:06 demo_variable.sh
-rw-rw-r-- 1 network network   174 Mar  2  2017 lynx.cfg
-rw-rw-r-- 1 network network 40960 Jan 15 13:21 test_script_archive1.tar
-rw-rw-r-- 1 network network 15953 Jan 15 13:21 test_script_archive2.tgz
drwxrwxr-x 6 network network  4096 Feb 10  2017 virtnet