Shell Scripting for beginners 💻

Shell scripting is a way to automate the tasks that is performed on a linux machine. There is a wide variety of application for this. From system administration, housekeeping and maintenance, testing, auto-recovery etc are some of most common requirements. It is a combination of simple commands and some programming elements such as loops, conditional statements, arrays, lists etc.

Lets looks at the common building blocks for a shell script and then we shall look at a some tricks that you might found useful while doing scripting.

First line in any shell script!

#!/bin/bash

The first line in any shell script would have a similar line (called as the shebang line or shell execute line) what this means is that there is a binary called “bash” inside “/bin” directory which is now-a-days a very popular shell (command line interpreter) program which we want to use.
Why we specify this is because lets assume your system has 2 different cli programs (say bash and sh) and you explicitly declare which shell you want to use.

Running a script

[root@localhost]# chmod +x my_script.sh # giving execute permissions to the script file
[root@localhost]# ./my_script.sh

[root@localhost]# sh my_script.sh

Firstly ensure you have given execute permission to your script then use either the “./<script_file_name>” method or “sh <script_file_name>” method.
when you run “sh <script_file_name>” then the shebang line is ignored

Declaring a variable

[root@localhost]# n=10
[root@localhost]# name="Kevin"

Bash scripting is dynamically typed so no need to specify the data type before the variable name. An integer can be specified without any quotes but a string has to be wrapped inside Quotes (both single and double quotes are allowed)

Invalid declarations

[root@localhost]# name= "Kevin"
-bash: Kevin: command not found
[root@localhost]# name = "Kevin"
-bash: name: command not found
[root@localhost]# n= 10
-bash: 10: command not found

Spaces in between variable name and its values are not allowed.

If Statement

#!/bin/bash
n=20
if [ $n -eq 20 ]        
      
 # -eq is for equals
 # other options are lt=less than, gt=greater than, le=less or equals, ge=greater or equals
        then
        echo "n is greater than 20"
fi
name="kevin"
if [ "$name" == "kevin" ]       # while comparing strings use "==" or "!="
        then
        echo "the name is kevin"
fi

Note that spacing is very important, though there is no indendation as such but “if [$n -eq 20]” would not work. And while validating string its required to wrap around quotes while integer values do not require wrapping

Combined If Statements

# AND
if [ $n -ge 20 ] && [ "$name" == "kevin" ]
        then
        echo "n is greater than 20 and the name is kevin"
fi
# OR
if [ $n -ge 20 ] || [ "$name" != "Adam" ]
        then
        echo "either n is greater than 20 or the name is kevin"
fi

Nested If Statement

n=20
name="kevin"
if [ $n -ge 20 ]
        then
                if [ "$name" == "kevin" ]
                then
                  echo "the name is kevin"
                fi
        echo "n is greater than 20"
fi

Else-If and Else Statements

n=10
m=20
if [ $n -gt 15 ]
        then
                echo "n is greater than 15"
elif [ $m -gt 25 ]
        then
                echo "m is greater than 25"
else
                echo "both the above conditions are not met"
fi

For loop

for i in 0 1 2
        do
                echo $i
        done
output ::
0
1
2
3

Nested For loop

for i in 0 1  # first loop
        do
           for j in 2 3   # second loop
                 do
                     for k in 4 5  # third loop
                        do
                           echo "$i $j $k"
                        done
                 done
        done
output ::
0 2 4
0 2 5
0 3 4
0 3 5
1 2 4
1 2 5
1 3 4
1 3 5

you can nest even further levels of statements

Breaks in For loop

for i in 1 2 3
        do
                if [ $i -eq 2 ]
                        then
                                break
                else
                        echo $i
                fi
        done
output ::
1

a break in a loop (for and while) would mean to come out of the loop

Continue in For loop

for i in 1 2 3
        do
                if [ $i -eq 2 ]
                        then
                                continue
                else
                        echo $i
                fi
        done
output ::
1
3

a continue in a loop (for and while) would mean to skip the current iteration

While loop

n=5
while [ $n -lt 10 ]     # -lt : less-than
        do
                echo "$n"
                (( n = n + 1 ))    # Incrementing n by 1 
        done
output ::
5
6
7
8
9

Arrays

[root@localhost]# array=(a b c)       # Declaration
[root@localhost]# echo ${array[@]}    # printing the complete array
[root@localhost]# a b c
[root@localhost]# echo ${array[0]}    # Value at index 0
a
[root@localhost]# echo ${array[1]}    # Value at index 1 
b
[root@localhost]# array[0]='d'        # Updating the value of array at index 0
[root@localhost]# echo ${array[@]}
[root@localhost]# d b c

Functions

#!/bin/bash
my_func() {
a=10
echo $a
}
my_func # Calling a function
output ::
10

Passing values to functions

#!/bin/bash
my_func() {
first_arg=$1
second_arg=$2
echo "First Argument=$first_arg and second argument=$second_arg"
}
my_func 10 20 # Calling function with 2 arguments
output ::
First Argument=10 and second argument=20

Returning values

[root@localhost]# cat my_script.sh 
#!/bin/bash
my_func() {
  a=10
}
my_func
echo $a
[root@localhost]# sh my_script.sh 
10

In shell we cannot return a values from a function while any variable declared inside the function can be freely used outside the function

An important rule!

[root@localhost]# cat my_script.sh
#!/bin/bash
my_func
my_func() {
  a=10
}
echo $a
[root@localhost]# sh my_script.sh
my_script.sh: line 2: my_func: command not found

An important fact to note here is that in shell scripting we cannot call a function before its defined. So the positioning matters!

Integer arithmetic

[root@localhost]# n=1
[root@localhost]# (( n = n + 1 ))
[root@localhost]# echo $n
2
# or, even expr can be used, Please use "`" and not "'"
[root@localhost ~]# n=1 
[root@localhost ~]# m=`expr $n + 1`
[root@localhost ~]# echo $m
2

note- spacing needs to be maintained, else the code will not work

Floating point arithmetic

[root@localhost]# bc <<< "scale=4; (20.222+5.0)/2.54"
9.9299

scale is the option for precision level.

Assigning the output of shell commands to variables

[root@localhost]# ls_output=`ls`
[root@localhost]# date_output=`date`
[root@localhost]# echo $ls_output
ftp games gdm lib local lock log mail opt preserve run spool tmp www yp
[root@localhost]# echo $date_output
Fri Aug 24 17:18:23 IST 2018

note the presence of “`” (back quote symbol)

Exiting a script

n=4
if [ $n -eq 4 ]
        then
                exit 0
fi

Understanding exit codes

[root@localhost utility]# date
Fri Aug 24 17:55:59 IST 2018
[root@localhost utility]# echo $?
0
# 0 means successfully executed
[root@localhost utility]# datex
-bash: datex: command not found
[root@localhost utility]# echo $?
127
# 127 means command not found
[root@localhost utility]# which datex
/usr/bin/which: no datex in (/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin)
[root@localhost utility]# echo $?
1
# 1 means the results is not success but command was executed
[root@localhost utility]# ls unknown_file
ls: cannot access unknown_file: No such file or directory
[root@localhost utility]# echo $?
2
# 2 means file not found

many times we want to see if our operations in the script is success or failed based on that we can choose some actions, knowing exit codes in that cases will be handy

Handling output streams

[root@localhost bin]# pwd
/bin
[root@localhost bin]# ls cut cp cpzzz
ls: cannot access cpzzz: No such file or directory
cp  cut
[root@localhost bin]# ls cut cp cpzzz 2>/dev/null
cp  cut

see that we are in /bin directory where all the command binaries are kept and we know that “cpzzz” is not a valid command
How linux handles this is it gives the command output in the standard_output stream while the error output is passed using the standard_error stream, we can redirect (using > symbol) the error stream (2) to a null device (called /dev/null)

A tiny script to delete all files in current directory

#!/bin/bash
list_of_files=`ls`
for i in $list_of_files
        do
                echo "Deleting file-$i"
                rm -f $i
        done

Another script example

This script checks various parameters in a system config file (/etc/sysctl.conf) for the four parameters (net.core.wmem_default, net.core.rmem_default, net.core.wmem_max and net.core.rmem_max) and checks if their values are corresponding to (1024000, 1024000, 10485760 and 10485760). If not it will change the values to the one given in the declared array.

Thanks for reading.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *