Bash Scripting

Here I will explain Bash scripting from intermediate to a very advanced level.

Introduction

Shell in a very simple term is a program (interpreter) that takes an input (directly from keyboard or from a file), process it and then generates output. There are several shell types available in Linux:

  • bash (bourne-again shell)
  • csh (c shell)
  • ksh
  • tcsh

In order to find out all shells available on your system:

  • hossein@ubuntu:~$ cat /etc/shells
    # /etc/shells: valid login shells
    /bin/sh
    /bin/dash
    /bin/bash
    /bin/rbash

I would say more or less sh, bash and dash are similar and sometimes in some distribution /bin/sh is referring to /bin/bash or /bin/dash. As an example in my ubuntu machine:

  • hossein@ubuntu:~$ ll /bin/sh
    lrwxrwxrwx 1 root root 4 Nov 21 00:39 /bin/sh -> dash*

I prefer to use bash as the interpreter for my terminal which is also the default shell in most of the Linux distributions:

  • hossein@ubuntu:~$ echo $SHELL
    /bin/bash

So it means that no matter how we interact with the system through terminal, it always use bash as the interpreter. Even if we write several lines of commands and put it in a file (calles shell script) and then run it, it will use bash as the interpreter. However to make it sure that script always use the bash as the interpreter, we can add “#!/bin/bash” at the begging of the file. Let’s have simply example:

  • rouhani@cassius scripts $ cat readFile
    #!/bin/bash
    Line_num=1
    while read Line
    do
    echo “${Line_num}: ${Line}”
    ((Line_num++))
    done < /etc/fstab

Above we have a “shell script” called “readFile” which is a bunch of lines (collection of instructions) that is designed to be run by Shell. At the first line I specifically mentioned to use bash as the interpreter. There is another way of forcing the script to use bash as the interpreter at the time of running, by using “bash” before the script name:

  • rouhani@cassius $ bash readFile

 

Shell Scripting – intermediate to advanced

Learning the shell scripting is very easy. Here in 12 steps I will go through all you need to know to write your own script and debug it.

1. Variable
A variable is a character string to which we assign a value to it. We can simply assign a value like this:

  • VariableName=”value”

and access the value like:

  • $VariableName or ${VariableName} or print it like: echo $VariableName

we can assign the outcome of a command to a value as follow:

  • VariableName=$(command)

 

2. Condition testing
We can use [] for testing a condition. There are some keywords that can be used in conjunction with []. As an example, following return True if a file (/etc/passwd) exists:

  • [-e /etc/passwd]

Other keywords which are useful are:

  • -d File: True if file is directory
  • -f File: True if file exist and is a regular file
  • -r File: True if file is readble by u
  • -w File: True if file is Writable
  • -x File: True if file is executable
  • -s File: True if file exist and is not empty

Other useful keywords that are very common in shell scripting are:

  • && = AND if the first command executed then second will also
  • || = OR only one of the command will be executed
  • ; all command will be anyway executed in row
  • -ge value is greater or equal
  • -le value is less or equal

 

3. If condition

Following is the example of a if condition in a most complex way possible. Off course we can remove elif and else in a simple kind of conditions.

  • if [ condition is true ]
    then
    echo “Cool Beans”
    elif [ condition is true]
    then
    echo “Neato cool”
    else
    echo “Not Cool Beans”
    fi

If we want to use many if/elif we can alternatively use case statement which I will mention later.

 

4. for loop

The best is to see different ways of using it with examples:

  • for VARIABLE in 1 2 3 4 5 .. N
    do
    command1
    command2
    command N
    done
  • for Color in red blue green
    do
    echo “Color: $Color”
    done
  • for OUTPUT in $(command)
    do
    echo $OUTPUT
    done

 

5. Accepting User input

There are 2 ways for this purpose:

a. positional parameters

  • # script.sh param1 param2 param3
    $0: shell script itself
    $1: “param1”
    $2: “param2”

So in this way, inside our script we can use $1, $2, … to get the value of param1, param2 and etc. Let’s have an example:

 

  • rouhani@cassius $ cat Positional.sh
    #!/bin/bash
    echo “the first parameter you entered is: $1”
    echo “the second parameter you entered is: $2”

 

  • rouhani@cassius $ Positional.sh Hossein Rouhani
    the first parameter you entered is: Hossein
    the second parameter you entered is: Rouhani

 

b. using the “read” command. Let’s have an example which will clear all your doubts:

  • rouhani@cassius scripts $ cat Positional-read.sh
    #!/bin/bash
    read -p “enter your name: ” Name
    read -p “enter your family name: ” FamilyName
    echo “the first parameter you entered is: $Name”
    echo “the second parameter you entered is: $FamilyName”

And now when we run it, it will prompt for a “enter your name” and “enter your family name” and will get the values for Name and FamilyName.

  • rouhani@cassius scripts $ Positional-read.sh
    enter your name: Hossein
    enter your family name: Rouhani
    the first parameter you entered is: Hossein
    the second parameter you entered is: Rouhani

 

7. Exit status

We should know that all Linux commands will return an exit status  after the execution which ranges from 0 to 255 which:

  • 0 = success
  • other than 0 = error

for status of other than 0, we can check the man page of the command to know what does that mean. As an example, we can check the “ls” command exit status code simply by having a look at “man ls” which shows following:

  • rouhani@cassius scripts $ man ls
    ….
    an Exit status:
    0 if OK,

 

  • 1 if minor problems (e.g., cannot access subdirectory),
  • 2 if serious trouble (e.g., cannot access command-line argument).

In most of the cases, what is important for us is “0” exit code which shows command has been executed successfully.

There is a good sign ($?) which shows the return code of previously executed command. As an example:

  • ls /home
  • rouhani@cassius scripts $ echo $?
    0
  • rouhani@cassius $ ls /home/ldd
    ls: cannot access /home/ldd: No such file or directory
  • rouhani@cassius $ echo “$?”
    2

Let’s have an example to use $? inside the script.

  • rouhani@cassius $ cat test3
    #!/bin/bash
    Host=”hrouhani.org”
    ping -c 2 $Host
    if [ “$?” -eq “0” ]
    then
    echo “The Host $Host is pingable”
    else
    echo “The Host $Host is not reachble”
    fi

we can write the above command in the short form as well with following command:

  • ping -c 2 $Host && echo “The Host $Host is pingable”

We can also catch the code and put it in a variable and then compare, like:


  • returnCode=$?
    If [ “$returnCode” -eq “0” ]
    ….

And interestingly the default value of the last command of the shell is the Exit code of the whole script which we can control in case of the need for exit command. As an example:

 

  • rouhani@cassius courseCommands $ cat test4
    #!/bin/bash
    ping -c 2 hrouhani.org > /dev/null
    if [ “$?” -ne “0” ]
    then
    echo “website is not reachable”
    exit 1
    fi
    exit 0

Now if we run the script, and want to know if “hrouhani.org” is accessible, we can simply “echo $?” after the script is finished execution. Here we don’t see the results of the “ping” command since we redirected the outcome to /dev/null, and only way to find out is using the exit code.

 

9. Function
As you might know, one of the important part of object-oriented programming is to re-use the same code for repetitive tasks. Here we can use function for this purpose, beside it help us to break down the script into smaller and logical subsection.
The are functions which can be defined in any of the following ways:

  • functionName() {


    }
  • function functionName() {


    }

Calling the function happens simply by using the name:
functionName

I will mention some of general features of the function which is more or less same in all programming languages:

a. Passing a parameter to a function
it is possible to write a function in a way that accept parameters while calling it. The parameters are represented by 1$, 2$, 3$ and so on, and additionally $@ contains all parameters. Let’s have some examples:

  • rouhani@cassius $ cat test5
    #!/bin/bash
    function ping-function() {
    ping -c 2 $1
    if [ $? -eq “0” ]
    then
    echo “Host is reachable”
    else
    echo “not pingable”
    fi
    }
    ping-function hrouhani.org

 

  • rouhani@cassius $ cat test6
    #!/bin/bash
    function ping-function() {
    for i in $@
    do
    ping -c 1 $i
    if [ $? -eq “0” ]
    then
    echo “The host: $i is reachable”
    fi
    done
    }
    ping-function hrouhani.org google.com

Similar to shell script, the function also has an Exit code (return code) which is the status of the last command executed in a function. To get the exit status of the function we can use $? after calling the function.

 

b. Returning values from function
simply we can use a “return value” from inside a function in order to return a value based on our decision. But it is important to know that if a function does not have “return value” line simply the return value is the exit status of the last command executed in the function which as always 0 means the last command executed successfully.

10. Case statement

This is alternative to if statement which I will simply show with an example:

 

  • hossein@ubuntu:~$ cat ssh
    #!/bin/bash
    read -p “enter your choice for ssh service start/stop/status: ” input_string
    case $input_string in
    start)
    /etc/init.d/ssh start
    ;;
    stop)
    kill $(cat /var/run/sshd.pid)
    ;;
    status)
    /etc/init.d/ssh status
    ;;
    *)
    echo “You have to enter start or stop”; exit 1
    ;;
    esac

 

  • hossein@ubuntu:~$ ./ssh
    enter your choice for ssh service start/stop/status: status
    ssh start/running, process 1067

It is also possible to remove the “read” line and change the case to following which accept the first argument as the input:

  • case $1 in
    start)
    …..

 

11. while loops

Its better to have some examples which beside getting to know the syntax will also show the different ways of using while loop:

  • while [ “$answer” != “yes”| “y” ]
    do
    read -p “Enter your Name: “ name
    read -p “is ${name} your name? “ answer
    done

In another example I use [] for while loop.

  • ping -c 1 hrhhouhani.org > /dev/null
    while [ $? == 0 ]
    do
    echo “hrouhani.org is Up …”
    sleep 2
    done

Another useful example would be reading a file (/etc/fstab) line by line:

  • Line_num=1
    while read Line
    do
    echo “${Line_num}: ${Line}”
    ((Line_num++))
    done < /etc/fstab

we can a bit change the above command and print only the lines which has ‘xfs’ keyword on it:

  • grep xfs /etc/fstab | while read Line
    do
    echo “the line which has xfs: ${Line}”
    done

Another beautiful example:

  • while true
    do
    read -p “1: system uptime. 2: disk usage of current directory. 3: today date ” choice
    case “$choice” in
    1)
    uptime
    ;;
    2)
    df -hT
    ;;
    3)
    date
    ;;
    4)
    break
    ;;
    esac
    done

 

12. Debugging

There is a time that we need to go through the script and find out the error, unexpected behavior or results. There are several options that bash script provides that help in debugging the script which we can easily see by using “help set”.

 

a. Using x-trace

This is the most famous one in debugging which prints the original command with their variable’s Values (after substitution). We can simply add -x to the first line of script as follow:

  • #!/bin/bash -x

we can also set debugging for a portion of the script by putting that part between “set -x” and “set +x”.

b. another option is “-e” option which cause the script to exit exactly on the point where there is an error in the script. Offcourse we can combine with other options as well like combing with “-x”:

  • #!/bin/bash -xe

example:

  • #!/bin/bash -ex
    file=”/etc/Hossein”
    ls $file
    echo $file

so if we execute the script, we will see that it will print each line with its value and plus when encounter an error will stop immediately:

  • rouhani@cassius scripts $ BugExample
    + BugExample
    + file=/etc/Hossein
    + ls /etc/Hossein
    ls: cannot access /etc/Hossein: No such file or directory

c. another option is “-v” option which print each line from script, so basically without any value substitution. It is specifically useful when combined with “-x” option as it prints the original line and the line with value substitution. Here in a same example, I combine with “-x” and “-xe” to see how useful it would be for debugging:

 

 

  • rouhani@cassius scripts $ BugExample
    #!/bin/bash -vx
    file=”/etc/Hossein”
    + file=/etc/Hossein
    ls $file
    + ls /etc/Hossein
    ls: cannot access /etc/Hossein: No such file or directory
    echo $file
    + echo /etc/Hossein
    /etc/Hossein

 

  • rouhani@cassius scripts $ BugExample
    + BugExample
    #!/bin/bash -vxe
    file=”/etc/Hossein”
    + file=/etc/Hossein
    ls $file
    + ls /etc/Hossein
    ls: cannot access /etc/Hossein: No such file or directory

 

 

 

 

%d bloggers like this: