Evolution of Bash, Writing and Executing Bash commands and Scripts


Hi, I am Malathi Boggavarapu working at Volvo Group and i live in Gothenburg, Sweden. I have been working on Java since several years and had vast experience and knowledge across various technologies.

In recent times i faced a situation where i need to write some bash scripts in my project that finally lead me to update my blog with Bash course. This course will guide you through various bash commands and also about writing bash scripts. This course addresses all the concepts with real time examples.

So let's get started.

What is Bash?


Well, Bash is Born Again Shell. It's is a very popular command line interpreter or shell. It has a very long history starting with first Unix shell called Thompson Shell in 1971. Thompson shell has very minimalised features and lack many important features and is replaced with Mashey Shell in 1975. That inturn was replaced by Bourne Shell (sh) in 1977 which have many features that we still use such as command substitution, various types of loop constructs and commands such as cd, pwd and export. And also it has break/continue and getopts and someother which we will discuss later in this section. Some of the features are got from other project ALGOL. However it was criticized for not being friendly to use and C shell (csh) is introduced in 1978 and had a style more similar to C language and Bourne shell is more similar to ALGOL.

In 1989 BASH was released as a free alternative to BOURNE shell under the new project which promotes the freedom of usage to run, share, study and modify software. BrainFox is the original author of Bash. Bash is the default shell on Mac OS and Linux making it incredibly widespread. As of now the current version of Bash is 4.4.

You can find more information about Bash at https://tiswww.case.edu/php/chet/bash/bashtop.html.



Reviewing common Bash commands


pwd - Prints the current working directory. That means it tells you where you currently are.
ls - Lists files and folders in current working directory
ls -l [Folder/File name] - If folder name is given, it lists all the files inside the folder along with Permissions, File creation/updation date and so on. If the file name is given, the same file will be shown  with Permissions, File creation/updation date and so on.


In the above picture, the first column shows the permissions of the directory or the file. Also the starting character d in the first column indicates that it is the directory. Otherwise it simply shows -
man ls - It shows all the options available to use with ls command. man command provides the comprehensive guide for all the commands.
mkdir [folder] - Creates a directory with the specified folder name
rmdir[folder] - Deletes the directory
clear - Clears the screen
cd [dir_name] - Change to the specified directory
cp [src_file_name] [dest_file_name] - Creates a copy of src_file_name and is named with dest_file_name
rm [file_name] - remove the specified file
cat [file_name] - See the contents of the file
more [file_name] - You can see the long file in the form of pagination. That means you can see the file page by page. Press Space bar to move through the pages and then press q when you are done.
head [file_name] - Peek at the beginning of the file.
tail [file_name] - View the end of the file. This is very handy for the log files
cd ~ Points to the user home variable
cd~- is the PWD
echo ~- shows the PWD

The other handy expansion is called brace expansion.
Ex: touch{apple, banana, cherry, pineapple}
It creates the files with the specified names. You no need to run touch command multiple times. For suppose if i want to create 1000 files then it would be tedious to run touch command those many times. So i can do it in the easy way. See below

touch file_{1..1000}

Try the command and check whether the files are created with command ls

echo{1..10} - Prints the numbers from 1 to 10
rm * - Removes all the files in the existing directory
echo{1..10..2} - output will be 1 3 5 7 9
echo{1..10..3} - output will be 1 4 7 10
echo{A..Z} - Output from A - Z
echo{A..z} - Output from A - Z following with a - z
touch {apple, banana, cherry}_{01..100}_{w..d} - Create files with those three combinations. Just try the command and see.


Pipes

It is one of the powerful feature that is used to run multiple commands at a time

ls | more - List of files will be displayed in the form of pagination. Press Spacebar to move page by page.

Example: I create a new folder called otherfolder and change the permissions of some of the files (files which match the pattern _015) in the current folder. Later i copy the files to otherfolder from current folder using cp. -v option along with cp command means to turn on verbose. It means that it displays copy command output for each of the file it copies to the otherfolder. I am specifying * to specify file name expansions to match all of the files.

mkdir ../otherfolder
chmod 000 *_015*
cp -v * ../otherfolder

If you execute the above command, you will see that copy operation for the files which match the pattern _015 are not copied because they have restricted file permissions. I did that using chmod in the above example.

cp -v * ../otherfolder 1>../success.txt 2> ../error.txt

The number 1 and 2 represent standard output and standard error respectivly. > symbol represents redirecting the output to the success.txt and error.txt. So all the files which were copied successfully to the otherfolder will be written to success.txt and the files which were not copied to otherfolder will be written to the file error.txt.
cat ../success.txt - Shows the contents of the file
cp -v ../otherfolder &> ../log.txt - This command instead of using 1  and 2 as shown in the above command just use &> to store all the output of command to the file log.txt.


Grep

This is a handy tool which allows to search files for dpecific pattern of text. This comes in handy when you are searching through the log files.

grep --color=auto malathi auth.log - auth.log is the log file which have my name malathi. when i execute this command, my name malathi is highlighted with a color.

export GREP_OPTIONS='--color=auto' - This is another way to enable color=auto option of grep command. But the option is enabled at all the time. When grep command is executed, the content of the auth.log file will be displayed and the search word will be highligted with a color

grep -i break-in auth.log -
grep -i break-in auth.log | awk{'print $12'} -
man awk
man sed


Understanding Bash script syntax

Bash script syntax

#! /bin/bash
# comment
      commands
            more commands

     other commands

The script will start with interpretor directive that sometimes called hashbang or shebang. The nick name comes from first two characters in the line #!.  bin/bash is the path of the bash executable and this vary on different systems but usually it will be a bin/bash. The whole line tells the shell that this is a bash script and should be executed as such. For example if you are trying to execute bash script in cygwin, this piece of line indicates that it should be run as bash script by the shell.

# sign is used to comment out the lines. The interpretor will just ignore such lines.

Example: I am using cygwin inorder to write bash script and execute them. Please download cygwin and install it in your machines. After installing, open the cygwin shell and create a bash script.

> vi my.sh

#! bin/bash
# This is a sample bash script
ls

Save the file by pressing :wq. Now you will be returned back to the terminal. Now execute the bash script using the command

bash my.sh

This list all the files in the current working directory. You can also execute the bash script using ./my.sh but you will get an error saying permission denied. So you need to change the permissions of the script using the command

chmod +x my.sh

Now if you run the bash script again using ./my.sh then it executes successfully without any error.

We need to put ./ because the current working directory is not in PATH environment variable. If we copy the file which is the in PATH environment variable, then you just need to type my.sh

> cp my.sh /usr/bin
> my.sh

But we will not do it practically. Just for the knowledge i am making note of it here.


Working with variables


We will start with a bash script example and go through it.

#! /bin/bash
# This script demonstrates about variables
a=Hello    
b="Good Morning"
c=17

echo $a
echo $b
echo $c
echo "$b! I have $c apples."

As you notice you won't see spaces around = sign when you declare variables a,b and c. If you add spaces around them, error will be thrown when you run the script. So please make note that there are no spaces around = sign when you declare variables. If there are spaces in the Strïng then you need to enclose the Strïng within double quotes. If you notice a and b variables in the above script, variable a is without double quotes and b is with double quotes because String in b is with spaces. So we require double quotes to avoid error.

So finally to use these variables, we call them using $ sign infront of them. As you all know echo is used to print hte result to standard output. 

Adding attributes to variables

declare -i d=123 # Marks the variable as an integer
declare -r e=456 # Marks the variable as read only. It means you can not modify the value.
declare -l f="Cats" # USed to convert strings to lower case
declare -u g="Dogs" #USed to convert strings to upper case

Built-in variables

echo $HOME - returns the users home directory
echo $PWD - Returns the user current working directory.
echo $MACHTYPE - Returns the machine type which could come handy to determine file location if you are writing scripts to work on different platforms
echo @HOSTNAME - Returns the system name. 
echo $BASH_VERSION - Returns the version of bash running.
echo $SECONDS - Retuns the number of seconds the BASH session has been running. 
If you type echo $SECONDS at the command line, you will know how much time the session has been open. But if you use it in the script, it will start counting from when the script started. This is handy for timing things.
echo $0 - Returns the name of the script. If you use it inside of bash script, it prints the name of the script.

If you want to explore more, you can visit the following website



Working with numbers

Working with number in bash is pretty stright forward. To tell the interpretor to deal with Math, you need to enclose the expression inside (()). Example:: (( expression ))

Example

d=2
e=$((d+2))
echo $e
((e++))
echo $e
((e--))
echo $e
((e+=5))
echo $e
((e*=3))
echo $e
((e/=3))
echo $e
((e-=5))
echo $e

Let's take an another example usecase

f=$((1/3))

It actually prints 0 in the output because bash Math works only with integers not floating point number. To work with floating point values, you need to use bc program within the script.

Example

f=$(echo 1/3 | bc -l)
echo $f

If you run now, you will see the output as 0.3333333333. Just run it and see.
If you want to learn more about bc command, type man bc command and check.

Comparing values

Bash uses the following syntax to compare values. It is important to keep spaces aroung the expression.

[[ expression ]]

1: FALSE
0: TRUE

If the expression returns 1 then it is false and if it returns 0 then it is true.

Let's take an example for Comparing Strings

Example

#! /bin/bash
# This demonstrates bash String comparison operators
[[ "cat" == "cat" ]]
echo $?
[[ "cat" == "dog" ]]
echo $?

Below is the list of operators available for comparing Strings














Example for comparing integers

[[ 20 -gt 10]]
echo $?
[[ 30 -lt 10]]
echo $?

Below is the list of operators available for comparing Integers.











There are also Logical operators available in bash. See the picture below




There are also operators to test whether the String is NULL or NOT NULL. option -z is used to test whether the String is null or empty an option -n test whether the String is not null.

let's take an example

a=""
b="cat"
[[ -z $a && -n $b]]
echo $?

If you are finding examples online or working with older scripts you might see a notation with single square brackets. Double square brackets are introduced in bash version 2.2 onwards.


Working with strings

Concatenate Strings

a="hello"
b="world"
c=$a$b
echo $c

output: helloworld

To find the length of the String, use the following command.
echo ${#a}
output: 5

To extract substring of the String, use the following command. Note that the index will start from 0. The below command will output the substring of helloworld starting from index 3 to the end of the string.

d=${c:3}
echo $d

output: loworld

We can also specify start index and endindex to extract the substring of the given string. See below command. Here 3 is startIndex and 4 is endIndex.
e=${c:3:4} 
output: lowo

If we specify the negative number then it starts from the end of the String towards the starting of the String. The below command will extract the substring of String by traversing from end of the String until the index 4.

echo ${c: -4}
output: orld

The following command extract the first three letters of the last four letters in the String.
echo ${c: -4:3}
output: rld

The following command replaces the one String with the other. In the below example, it searches for the first occurence of the String banana and replaces that with pineapple. It does not replace the second occurence.

fruit="apple banana banana cherry"
echo ${fruit/banana/pineapple}
output: apple pineapple banana cherry

Now if you want to replace all the occurences of the String, use the following command. We need to just add two slashes (//) instead of one.

fruit="apple banana banana cherry"
echo ${fruit//banana/pineapple}
output: apple pineapple pineapple cherry

We also have couple of modifiers that we need to know. The below command have a sign # infront of apple. It actually search for apple in the very beginning of the String.

fruit="apple banana banana cherry"
echo ${fruit/#apple/durian}
output: durian banana banana cherry

If we want to replace the very end of the String, the following command works. It actually search for the cherry at the very end of the String.

fruit="durian banana banana cherry"
echo ${fruit/%cherry/durian}
output: durian banana banana durian

Coloring and Styling text

Let's see how to color  and style the output of the command using tput. But before that we need to know the the color codes and styling options. Color codes will raneg from 0 - 7 both for foreground and background.














Styling options are below













Example:

flashred=$(tput setab 0; tput setaf 1; tput blink)
red=$(tput setab 0; tput setaf 1)
none=$(tput sgr0)
echo -e $flashred"ERROR:  $none$red"Something went wrong."$none


Exploring some handy helpers: date and printf

date is not part of bash but it is really useful for certain kind of scripts. The date command gives you the format like below

Thu Oct 17 21:06:18 UTC 2018

But we can use date formatting options like below. Let's take some examples.

date + "%d-%m-%Y"
output: 17-10-2018

Inorder to extract only the time in the desired format, use the below command

date +"%H-%M-%S"
output: 21:07:31

you can always use man command to explore more about particualr command. So now i discuss little bit about printf command. While you can use echo to print the text but printf gives lot more options.

printf "Name:\t%s\nID:\t%04d\n" "Malathi" "12"

output: 

Name: Malathi
ID:       0012

In the above command \t is the TAB and \n is the newline character, %4d will pad the field ID with 4 zero's if we dont specify a number. If we specify a number of length less than 4 then it will prefix the number with those many number of zero's which as a whole make the number with the length 4.

printf "Name:\t%s\nID:\t%04d\n" "Malathi"
Name: Malathi
ID:       0000

Now let's write a bash script using date and printf commands

#! /bin/bash
# This demonstrates date and printf commands
today=$(date +"%d-%m-%Y")
time=$(date +"%H:%M:%S")
printf -v d "Current User: \t%s\nDate:\t\t%s @ %s\n" $USER $today $time
echo $d

output: 

Current User: Malathi
Date   : 02-06-2018 @ 21:16:55

There are lot of format specifiers to use. I encourage you to check them out at the following url
http://wiki.bash-hackers.org/commands/builtin/printf


Working with Arrays

Inorder to keep track of collection of things we use arrays. Let's take a look at some examples.

! /bin/bash
# This demonstrates Arrays
a=()
b=("apple" "banana" "cherry")
echo ${b[2]}

output: cherry

We can also set array values by index numbers. This is very useful because instead of adding elements to the end of the array by default, we can add an element at particular index.


! /bin/bash
# This demonstrates Arrays
a=()
b=("apple" "banana" "cherry")
echo ${b[2]}
b[5]="kiwi" # Add kiwi at index 5 in the array
b+=("Mango") # Add Mango to the end of the array
echo ${b[@]} # Prints the whole array
echo ${b[@]: -1} # It prints the last element in the array

output

cherry
apple banana cherry kiwi Mango
Mango

We can also create associate arrays. That is we can specify key and a value. See the below example

! /bin/bash
# This demonstrates Associated Arrays
declare -A myarray
myarray[color]=blue
myarray["Shirt"]="T-Shirt"
echo ${myarray["Shirt"]} is ${myarray["color"]}

output
T-Shirt is blue



Reading and Writing Text files

Working with text files in bash is easy.  Let's see some examples now

# it stores the text to file.txt. file.txt will be created automatically if it doesn't exists.
echo "Some text" > file.txt 

# The above command overrides the contents of the file but inorder to append the content use >>
echo "Some more text" >> file.txt


What about reading files

#! /bin/bash
# This demonstrates reading content from file line by line
i=1
while read f; do
     echo "Line $i: $f"
     ((i++))
done < file.txt

The most interesting thing is we can write a set of instructions in the text file, read and execute them automatically. Let's take some good example. Name the file something like ftp.txt

open mirrors.xmission.com  // connect to site 
user anonymous nothinghere // user as anonymous and password as nothinghere
ascii    // type of connection
cd gutenberg  // Change the directory to gutenberg
get GUTINDEX.00    // fetches the file

>ftp -n < ftp.txt

Here -n option allows to skip the login user being the current session user of shell and connect with whatever credentials the file contains.

Control Structures

First let's take a syntax and example about if loop

if expression
then
     echo "True"
elif expression2; then
     echo "Expression 2 is True"
fi
Try the below examples for better understanding.

Example 1

#!/bin/bash
# This demonstrates if loop
if [ $a -gt 4 ]; then
    echo $a is greater than 4!
else
   echo $a is not greater than 4!
fi

Example 2

In this example we use regular expression to check whether the String contains numbers. To do that we use =~ to indicate that it is uses regular expression.

#!/bin/bash
# This demonstrates if loop and basic regular expression
if [ [ $a  =~  [0-9]+ ] ]; then
    echo "There are numbers in the String"
else
   echo "There are no numbers in the String"
fi


Working with While and Until loops

Let's work with while and until loops now. Please try the below examples and see how the output looks.

#!/bin/bash
# This demonstrates while loop
i=0
while [ $i -le 10]; do
         echo i:$i
         ((i+=1))
done

j=0
until [ $j -ge 10 ]; do
     echo j:$j
     ((j+=1))
done

Working with For loops

Example 1

#!/bin/bash
# This demonstrates if loop
for i in 1 2 3
do 
   echo $i
done

Example 2

#!/bin/bash
# This demonstrates if loop
for i in {1..100}
do 
   echo $i
done

#!/bin/bash
# This demonstrates if loop
for i in {1..100..2}
do 
   echo $i
done

Example 4

#!/bin/bash
# This demonstrates if loop
for (( i = 0; i <= 10; i++))
do 
   echo $i
done

Example 5

#!/bin/bash
# This demonstrates if loop
arr=("apple" "banana" "cherry")
for i in ${arr[@]}
do 
   echo $i
done

Now we take a look at Associative arrays. Please note Associative arrays are available from Bash 4


#!/bin/bash
# This demonstrates if loop
declare -A arr
arr["name"]="Malathi"
arr["id"]="12"
for i in "${!arr[@]}"
do 
   echo "$i: ${arr[$i]}"
done

Now we work with command substitution in for loop. In our example we use ls command. For loop through the output of ls command and print out the result.

#!/bin/bash
# This demonstrates if loop
for i in $(ls)
do
   echo "$i: ${arr[$i]}"
done



Selecting behaviour using CASE


To test number of different things we could use lot of if statements but there is better way to do it. That is case statement. Let's take an example

#!/bin/bash
# This demonstrates if loop
a="dog"
case $a in
        cat) echo "Cat";;
        dog|puppy) echo "dog or puppy";;
        *) echo "No match!";;
esac


Using functions


As you all know how important the functions are in every language and now we see how functions are built inside bash script. Let's take a look at different examples of functions and how we pass arguments to function and access them.

Example 1

#!/bin/bash
# This demonstrates functions
function greet{
     echo "Hi there!"
}
echo "Greeting - "
greet

Example 2

We can also pass arguments to the functions. See below.

#!/bin/bash
# This demonstrates functions
function greet{
     echo "Hi there! $1 $2"
}
echo "Greeting - "
greet Malathi "How are you"

Example 3

#!/bin/bash
# This demonstrates functions
function numberthings{
   i=1
   for f in $@; do
         echo $i: $f
         ((i+=1))
   done
}

numberthings $(ls)
numberthings apple banana cherry

In above example $@ takes any number of arguments and while we are calling function numberthings we are passing command substitution. This actually passes list of the files in the current directory and finally inside the function we loop through them and echo each one of them.

Similarly we can also pass some array of values to function.

Working with arguments

If the user want to pass some input to the bash script from the shell, we can do it as below

Example 1: let's name the script as test.sh

#!/bin/bash
# This demonstrates passing arguments to the script from the shell.
echo $1
echo $2

Now while running the script, we pass input to the script or we can call them arguments too. In below example Malathi will be the first arguement and Boggavarapu will be the second argument to the script.

>test.sh Malathi Boggavarapu
output :
Malathi
Boggavarapu

Example 2: In this exmple we will see how to pass arbitrary number of arguments to the script and how to make the script ready to accept multiple number of arguments

#!/bin/bash
# This demonstrates passing arguments to the script from the shell.
for i in @$
do
   echo $i
done
echo "There are $# arguments"

>test.sh banana apple cherry kiwi orange
output: 
banana
apple
cherry
kiwi
orange
There are 5 arguments

Working with flags

If you have been working with bash command line environment for a while you are bound to know about flags. There are specific options where you can pass information to a program and they ususally start with a -. You can make use of them in bash script by getopts.

Example: let's name the file as my.sh

#!/bin/bash
# This demonstrates the usage of flags
while getopts u:p: option; do
          case $option in
                   u)  user=$OPTARG;;
                   p)  pass=$OPTARG;;
          esac
done
echo "User; $user / Pass: $pass"

we run the above script using the following command
./my.sh -u malathi -p secret
output:
User: malathi / Pass: secret

Example 2: If we specify some options without colon (:) between them, it means that the bash script verify whether the flags that are passed in are being used inside the bash script. See below program.

#!/bin/bash
# This demonstrates the usage of flags
while getopts u:p:ab option; do
          case $option in
                   u)  user=$OPTARG;;
                   p)  pass=$OPTARG;;
                   a) echo "Got the a flag";;
                   b) echo "Got the b flag";;                 
          esac
done
echo "User; $user / Pass: $pass"

we run the above script using the following command
./my.sh -u malathi -p secret -a
output:
User: malathi / Pass: secret
Got the a flag

./my.sh -u malathi -p secret -a -b
output:
User: malathi / Pass: secret
Got the a flag
Got the b flag

Example 3: If you specify : before u, it means that if we pass some unknown flag which is not defined inside the script then it goes to the case statement ?)

#!/bin/bash
# This demonstrates the usage of flags
while getopts :u:p:ab option; do
          case $option in
                   u)  user=$OPTARG;;
                   p)  pass=$OPTARG;;
                   a) echo "Got the a flag";;
                   b) echo "Got the b flag";;     
                   ?) echo "I dont know what $OPTARG is";;          
          esac
done
echo "User; $user / Pass: $pass"

./my.sh -u malathi -p secret -a -b -z
output:
User: malathi / Pass: secret
Got the a flag
Got the b flag
I dont know what z is

Getting input during execution

You just saw how to get user input at command line. Sometimes it is not practical to have all of the values you are looking for specified when you run the script. There is a way to get input during the execution of the script. That is using read. Let's take an example

#!/bin/bash
# This demonstrates getting input during execution
echo "What is your name?"
read name
echo "What is your password"
read -s pass # -s is silent. Whatever you write is not visible

read -p "what is your favourite sport?" sport # -p is used to write everything on one line
echo name: $name, Password: $pass, sport: $sport

Just try the example. You will understand how it works

There are some options as well and i encourage you to explore more at following url


Example 2

#!/bin/bash
# This demonstrates getting input during execution
select fruit in "apple"  "banana" "cherry" 
do
   echo "You selected $fruit"
   break
done

Run at command line using 
./my.sh
1)apple
2)banana
3)cherry
#? 3
output: You selected cherry

Example 3

#!/bin/bash
# This demonstrates getting input during execution
select fruit in "apple"  "banana" "cherry" 
do
   case $fruit in
          apple) echo "Apple is sweet";;
          banana) echo "Banana is also sweet";;
          cherry) echo "Cherry is sour";;
           *) echo "I don't know what the option is";;
   esac
done

Test the script as said in above example. 


Ensuring the response


We should also build some error tolerance in the scripts. What happens if the user does not enter any input and simply press enter? 

Example

#!/bin/bash
if [ $# -lt 3 ]; then
    cat <<- EOM
    This command requires 3 arguments;
    username, userid and favorite number
    EOM
else
   echo "Username: $1"
  echo "Userid; $2"
  echo "Favourite number: $3"
fi

If you don't provide any arguments while running the script, it shows the message for the user. Otherwise it prints the result.

Example 2: Please test it

#!/bin/bash
read -p "Favourite sport? " a
while [[ -z "$a" ]]; do
     read -p "I need an answer" a
done
echo "$a is selected"

Example 3: The below script accepts the year with only the desired format specified. We use regular expression to validate the input of the user. Just try the example and you will understand it better.

#!/bin/bash
read -p "What year? [nnnn] " a
while [[ ! $a =~ [0-9]{4} ]]; do
    read -p "A year, please [nnnn]" a
done
echo "Selected year $a"

We came to the end of the session. Hope it is helpful. Please post your comments below.
Happy Learning!!

Comments

Popular posts from this blog

Bash - Execute Pl/Sql script from Shell script

Gradle Fundamentals

Load Balancing using Spring Cloud Netflix Ribbon