Lab 5: Bash scripting
Exercise 1 - Bash basics
Task 1 - Bash scripting basics
- Use the
echo
command to display a line of text. echo has three important options -n
, -e
, and -E
. Read the echo manual.
Create a shell script lab_5.sh
and add the following to see the output when you use the various echo options.#!/bin/bash
echo -E "Printing text with newline. This is the default option."
echo -n "What happens when we print text without new line"
echo -e "\nEscaping \t characters \t to print\nnew lines for example"
Execute the script.$ bash lab_5.sh
- Single line comments begin with
#
. Append the following lines to lab_5.sh
.# Adding comments that do nothing
echo "Testing single line comments"
Execute the script to see what happens.
- You can use multi line comments. The format is to start with a colon
:
followed by the comments enclosed in single quotes '
. For example, append the following to lab_5.sh
.: '
This is a multi line comment
Nothing happens in this section
'
echo "Back to executable commands"
Execute the script to see what happens.
- Double brackets are used to do arithmetic tasks. For example, append the following to
lab_5.sh
# Add two numeric values
((sum = 12 + 24))
# Print the sum
echo $sum
- Get user input with the
read
command. This is normally used in combination with echo to print a prompt to the user. Append the following to lab_5.sh
.echo "What is your favorite fruit?"
read fruit
echo "Hey! I like $fruit too."
Task 2 - Bash loops and conditions
-
Create a simple while loop. Save the following script as while_loop.sh
.
#!/bin/bash
counter=1
while [ $counter -le 10 ]
do
echo $counter
((counter++))
done
echo All done
-le
is the same as <=
.
-
Create an until loop. The until loop will execute the commands within it until the condition becomes true. Save the following script as until_loop.sh
.
#!/bin/bash
counter=1
until [ $counter -gt 10 ]
do
echo $counter
((counter++))
done
echo All done
-gt
is the same as >
.
-
Create a simple for loop that iterates through the item in a list and prints each of them. Save the following script as for_loop.sh
.
#!/bin/bash
names='Liliya Peter Emili'
for name in $names
do
echo $name
done
echo All done
-
Bash for
loop can be written in “C-Style”. For example save the following script as uptime.sh
.
#!/bin/bash
# Loop of ten iterations to print system uptime every 2 seconds.
for ((i = 1 ; i <= 10 ; i++)); do
echo -e "$i.\t"$(uptime)
sleep 2
done
Execute the script and analyze the output.
-
The break statement tells bash to exit the loop. An example is shown in break.sh
below.
#!/bin/bash
counter=1
while [ $counter -le 10 ]
do
if [ $counter -eq 5 ]
then
echo Script encountered the value $counter
break
fi
echo $counter
((counter++))
done
echo All done
-eq
is the same as ==
.
-
The continue statement skips the cuttent iteration of the loop. Save the following as continue.sh
and run it.
#!/bin/bash
counter=0
while [ $counter -lt 10 ]
do
((counter++))
if [ $counter -eq 6 ]
then
echo Number 6 encountered, skipping to the next iteration
continue
fi
echo $counter
done
echo All done
-lt
is the same as <
.
Task 3 - If statements
-
If statements allow us to make decisions in our script. This is usually utilized with a comparison operator. Save the following as if_statement.sh
.
#!/bin/bash
echo -n "Enter the number: "
read number
if [ $number -gt 100 ]
then
echo That\'s a large number.
elif [ $number -gt 50 ]
then
echo Not so much.
else
echo The number is way too small.
fi
Task 4 - Bash functions
-
Create a simple function
$ fun () { echo "This is a function"; echo; }
-
To view the content of all functions defined in the shell
$ declare -f
-
To list all functions by its name
$ declare -F
-
To view the content of the one your function
$ declare -f fun
-
To remove the function
$ unset fun
-
Now lets create a script with a 2 functions inside it
$ vim funky.sh
-
Inside vim type the following
#!/bin/bash
JUST_A_SECOND=3
funky ()
{ # This is about as simple as functions get.
echo "This is a funky function."
echo "Now exiting funky function."
} # Function declaration must precede call
fun ()
{ # A somewhat more complex function.
i=0
REPEATS=30
echo
echo "And now the fun really begins."
echo
sleep $JUST_A_SECOND
while [ $i -lt $REPEATS ] #use as (<,>,=) or (-lt, -gt, -eq)
do
echo "----------FUNCTIONS---------->"
echo "<------------ARE-------------"
echo "<------------FUN------------>"
echo
let "i+=1"
done
}
add_fun()
{
# A function just to add numbers
echo $((2+2))
}
#Now, call the functions
funky
fun
echo The return value of add_fun is: $(add_fun)
echo exit $? #check your exit status of the last function/command: if 0-success, otherwise is not
-
Run the script
$ bash funky.sh
-
Notice how the functions are only available within the one shell (once you exit it is gone for the parent shell)
$ declare -F
Exercise 2 - Working with files and directories
Task 1 - File and directory test operators
There are several options in bash to check the type of file you are interacting with. In many cases, the options are also used to check for the existence of a specified file or directory. The example below shows the options that can be used.
- Create a script
file_checker.sh
and add the following code:#!/bin/bash
if [[ $# -le 0 ]]
then
echo "You did not pass any files as arguments to the script."
echo "Usage:" "$0" "my-file-1 my-file-2"
exit
fi
for arg in "$@"
do
# Does it actually exist?
if [[ ! -e "$arg" ]]
then
echo "* Skipping ${arg}"
continue
fi
# Is it a regular file?
if [ -f "$arg" ]
then
echo "* $arg is a regular file!"
else
echo "* $arg is not a regular file!"
fi
[ -b "$arg" ] && echo "* $arg is a block device."
[ -d "$arg" ] && echo "* $arg is a directory."
[ ! -d "$arg" ] && echo "* $arg is not a directory."
[ -x "$arg" ] && echo "* $arg is executable."
[ ! -x "$arg" ] && echo "* $arg is not executable."
[[ -h "$arg" ]] && echo "* $arg is a symbolic link."
[ ! -h "$arg" ] && echo "* $arg is not a symbolic link."
[[ -s "$arg" ]] && echo "* $arg has nonzero size."
[ ! -s "$arg" ] && echo "* $arg has zero size."
[[ -r "$arg" && -d "$arg" ]] && echo "* $arg is a readable directory."
[[ -r "$arg" && -f "$arg" ]] && echo "* $arg is a readable regular file."
done
- Run the script and specify the files you want to check as arguments.
# bash file_checker.sh /bin/i386 /etc/passwd
You should get output similar to the following:* /bin/i386 is a regular file!
* /bin/i386 is not a directory.
* /bin/i386 is executable.
* /bin/i386 is a symbolic link.
* /bin/i386 has nonzero size.
* /bin/i386 is a readable regular file.
* /etc/passwd is a regular file!
* /etc/passwd is not a directory.
* /etc/passwd is not executable.
* /etc/passwd is not a symbolic link.
* /etc/passwd has nonzero size.
* /etc/passwd is a readable regular file.
Task 2 - Directory and file manipulation
-
You can create a new directory in bash with the mkdir
command. Save the following script as mkdir_bash.sh
.
#!/bin/bash
echo "Enter directory name"
read newdir
`mkdir $newdir`
-
You can check for the existence of a directory before proceeding to create it. Update mkdir_bash.sh
to look like the script shown below.
#!/bin/bash
echo "Enter directory name"
read newdir
if [ -d "$newdir" ]
then
echo "Directory exist"
else
`mkdir $newdir`
echo "Directory created"
fi
-
Create a bash script to read every line of a specified file. The file name is passed as a command line argument. Save the following script as file_reader.sh
.
#!/bin/bash
file=$1
if [[ "$file" == "" || (! -f "$file") ]]
then
echo Using standard input!
file="/dev/stdin"
fi
while read -r line
do
echo "$line"
done < "${file}"
The script reads the first value passed as a command line argument, represented by $1. If a text file is passed, the script will read and output each line of text.
If no command line argument is passed or if the file does not exist, standard input (/dev/stdin) is used instead. This will prompt you to enter text and will output to the terminal screen what is received as input. To signal the end of your stdin input type CTRL+D
Use this script to read /etc/passwd
.
$ bash file_reader.sh /etc/passwd
-
The Internal Field Separator (IFS) is used to recognize word boundaries. The default value for IFS consists of whitespace characters. Whitespace characters are space, tab and newline. Add the following script to the file ifs_test.sh
.
#!/bin/bash
mystring="foo:bar baz rab"
for word in $mystring; do
echo "Word: $word"
done
Run the script.
-
The default value for the IFS can be changed. Modify ifs_test.sh
to contain the following.
#!/bin/bash
IFS=:
mystring="foo:bar baz rab"
for word in $mystring; do
echo "Word: $word"
done
-
Let’s do this on a larger scale. Read /etc/passwd word by word while using :
as the IFS. Save the following as ifs_word.sh
.
#!/bin/bash
if [[ $# -le 0 ]]
then
echo "You did not pass any files as arguments to the script."
echo "Usage:" "$0" "my-file"
exit
fi
IFS=:
file=$1
if [ ! -f "$file" ]
then
echo "File does not exist!"
fi
for word in $(cat "${file}")
do
echo "$word"
done
Run the script specifying /etc/passwd
as an argument.
$ bash ifs_word.sh /etc/passwd
Task 3 - Jump directories
Sometimes it is difficult to navigate directories with the possibly infinite number of parent directories we need to provide. For example cd ../../../../../
. Let’s create a script that will help us jump to a specified directory without executing cd ../
.
- Create the script
jump_dir.sh
.# !/bin/bash
# A simple bash script to move up to desired directory level directly
function jump()
{
# original value of Internal Field Separator
OLDIFS=$IFS
# setting field separator to "/"
IFS=/
# converting working path into array of directories in path
# eg. /my/path/is/like/this
# into [, my, path, is, like, this]
path_arr=($PWD)
# setting IFS to original value
IFS=$OLDIFS
local pos=-1
# ${path_arr[@]} gives all the values in path_arr
for dir in "${path_arr[@]}"
do
# find the number of directories to move up to
# reach at target directory
pos=$[$pos+1]
if [ "$1" = "$dir" ];then
# length of the path_arr
dir_in_path=${#path_arr[@]}
#current working directory
cwd=$PWD
limit=$[$dir_in_path-$pos-1]
for ((i=0; i<limit; i++))
do
cwd=$cwd/..
done
cd $cwd
break
fi
done
}
- Make the script executable
$ chmod +x jump_dir.sh
- Add it to the .bashrc file to make it available on every terminal session.
echo "source ~/jump_dir.sh">> ~/.bashrc
- Open a new terminal and try jumping.
$ jump directory_name
Exercise 3 - Hash tables and more bash usage
Task 1 - Hash tables in bash
A dictionary, or a hashmap, or an associative array is a data structure used to store a collection of things. A dictionary consists of a collection of key-value pairs. Each key is mapped to its associated value.
- To declare a dictionary variable in bash, use the
declare
statement with the -A
option which means associative array. For example:$ declare -A newDictionary
- We have declared a variable called
newDictionary
. The following syntax can be used to add key-value pairs to the dictionary:$ newDictionary[key]=value
- Let’s add a key-value pair to the dictionary using the syntax above:
$ newDictionary[1]=SNA
$ newDictionary[2]=IML
- To retrive the value of a dictionary key, we have to add
$
with braces {}
to the dictionary variable we defined.$ echo ${newDictionary[1]}
- To update the value of a key, we simply overwrite the existing value by writing to the key again. For example, to update
newDictionary[1]
, we do the following:$ newDictionary[1]=NEWSNA
Retrieve the value of they key [1]
to verify the update.$ echo ${newDictionary[1]}
NEWSNA
- Use the
unset
command to remove a key-value pair from the dictionary.$ unset newDictionary[1]
Verify that the pair has been removed from the dictionary.$ echo ${newDictionary[1]}
- You can iterate through the dictionary by creating a
for
loop. An example is shown in the script below:#!/bin/bash
declare -A newDictionary
newDictionary[1]=SNA
newDictionary[2]=IML
newDictionary[3]=SSN
for key in "${!newDictionary[@]}"; do
echo "$key ${newDictionary[$key]}"
done
You should get an output similar to the following when you run the script:3 SSN
2 IML
1 SNA
- You can also declare and instantiate a dictionary in one line.
$ declare -A innoCourses=([1]=SNA [2]=IML [3]=SSN)
Check the content of the dictionary.echo ${innoCourses[3]}
Task 2 - Use sed in bash
Task 3 - Execute Python commands in bash
- Python can run one-liners from an operating system command line using option
-c
. An example is shown with the command below:$ python3 -c "a = 2; b = 4; sum = a + b; print('Sum =', sum)"
or
$ python -c "a = 2; b = 4; sum = a + b; print('Sum =', sum)"
The following bash script executes the python command above.#!/bin/bash
python3 -c "a = 2; b = 4; sum = a + b; print('Sum =', sum)"
- Alternatively, you scan split your python code into multi-lines by declaring a variable in the following fashion:
#!/bin/bash
py_script="
a = 2
b = 4
sum = a + b
print('Sum =', sum)
"
python3 -c "$py_script"
Exercise 4 - Debugging bash scripts
Task 1 - Command exit code
You can verify whether a bash command executed successfully by viewing the exit status code of the command. The exit status of the previously executed command is stored in the $?
variable. A successful command returns a 0
, while an unsuccessful one returns a non-zero value that usually can be interpreted as an error code.
- Run the command
$ ls -lah
.
- View the exit status of the previous command with
$ echo "$?"
.
- Now run a command that will fail. For example:
$ ls -lah /directorythatdoesnotexist
- Run
$ echo "$?"
again to view the exit status. You should get a value that is not 0
.
Task 2 - Using set -xe
When there is an error that stops the execution of a bash script, the bash interpreter usually displays the line number that triggered the error. However, in some cases, it might be necessary to trace the flow of execution of the script. This provides more insight into the conditions that are met, and the state of the loops.
- The
set
command is used to set or unset shell options or positional parameters.
- We can use the
set -e
option to exit the bash script if any command or statement generates a non-zero exit code. This is defined at the start of the script and it applies globally to all commands in the script.
- Additionally, we can also use the
set -x
option to display commands and arguments before they are executed. With this option, we can see every line of command that is executing in the script.
- The
set -e
option and -x
options can be combined to become useful debugging tools. The option -e
exits the script as soon as an error is encountered, and the option -x
shows the command that was running when the error was encountered.
- Create a bash script
loop_debug.sh
and add the following to it:#!/bin/bash
set -xe
# This script prints all user profiles.
FILE=".bashrc" # File containing user profile,
#+ was ".profile" in original script.
for home in `awk -F: '{print $6}' /etc/passwd`
do
[ -d "$home" ] || continue # If no home directory, go to next.
[ -r "$home" ] || continue # If not readable, go to next.
(cd $home; [ -e $FILE ] && less $FILE)
done
exit 0
- Run the script and analyze the output. Did the script stop prematurely?
- Remove the option
-e
and run the script again. Did you notice any difference? Why is it different?
- Now completely remove
set -xe
to see how the program executes without these options.
Questions to answer
- Upload the shell script files to moodle.
- Test every feature in your script and show screenshots of the test in your report.
-
Write a bash script that displays the following details of the logged-in user from the environment variables:
- Login username
- Home directory
- Shell
- The hostname of the system
- The script should extract the IP address of the system from the
ifconfig
or ip
command. Save the IP address to the ipaddress
variable and display it as output.
Sample output:
Username: user1
Home Directory: /home/user1
Shell: /bin/bash
Hostname: ubuntuvm
IP address: 10.1.1.1
-
Backups are important in system administration. Create a script that will backup your home directory.
- The backup file should be compressed to
tar.gz
.
- All files and directory permissions should be preserved in the backup.
- The backup destination directory is
/var/backups/
- The script should create the destination directory if it doesn’t already exist.
- The backup file name should take the format
home_backup_month_day_year_hour_minute_second.tar.gz
.
For example home_backup_Feb_18_2023_02_30_02.tar.gz
A typical real life scenario is keeping backups of websites. Administrators are usually interested in backing up /var/www/html
.
-
Write a bash script that checks various artifacts on the system. The script mainly checks for system information, and OS components. Your script should do the following:
- Print the OS kernel name and kernel version.
- Print the system architecture.
- Print all currently logged in users (show the date or time which the users logged in, and show the command line of the users’ current process).
- Verify that EFI is enabled and print the relevant output.
- List all connected block devices (Bonus: Identify the devices that have the GPT partition by adding an
*
to them in the output).
- List the first boot device on your system. This should be done according to the boot order in the NVRAM.
Ensure that the output of your script is neatly formatted and easy to read.
Bonus points if you create and use at least three functions.
Bonus
- Write a bash script that scans the entire system for files that contain the string “/bin/bash”. The script should print only the matches that the currently logged in user has execute permission on.
Lab 5: Bash scripting
Exercise 1 - Bash basics
Task 1 - Bash scripting basics
echo
command to display a line of text. echo has three important options-n
,-e
, and-E
. Read the echo manual.Create a shell script
lab_5.sh
and add the following to see the output when you use the various echo options. Execute the script.#
. Append the following lines tolab_5.sh
. Execute the script to see what happens.:
followed by the comments enclosed in single quotes'
. For example, append the following tolab_5.sh
. Execute the script to see what happens.lab_5.sh
read
command. This is normally used in combination with echo to print a prompt to the user. Append the following tolab_5.sh
.Task 2 - Bash loops and conditions
Create a simple while loop. Save the following script as
while_loop.sh
.Create an until loop. The until loop will execute the commands within it until the condition becomes true. Save the following script as
until_loop.sh
.Create a simple for loop that iterates through the item in a list and prints each of them. Save the following script as
for_loop.sh
.Bash
for
loop can be written in “C-Style”. For example save the following script asuptime.sh
.Execute the script and analyze the output.
The break statement tells bash to exit the loop. An example is shown in
break.sh
below.The continue statement skips the cuttent iteration of the loop. Save the following as
continue.sh
and run it.Task 3 - If statements
If statements allow us to make decisions in our script. This is usually utilized with a comparison operator. Save the following as
if_statement.sh
.Task 4 - Bash functions
Create a simple function
To view the content of all functions defined in the shell
To list all functions by its name
To view the content of the one your function
To remove the function
Now lets create a script with a 2 functions inside it
Inside vim type the following
Run the script
Notice how the functions are only available within the one shell (once you exit it is gone for the parent shell)
Exercise 2 - Working with files and directories
Task 1 - File and directory test operators
There are several options in bash to check the type of file you are interacting with. In many cases, the options are also used to check for the existence of a specified file or directory. The example below shows the options that can be used.
file_checker.sh
and add the following code:Task 2 - Directory and file manipulation
You can create a new directory in bash with the
mkdir
command. Save the following script asmkdir_bash.sh
.You can check for the existence of a directory before proceeding to create it. Update
mkdir_bash.sh
to look like the script shown below.Create a bash script to read every line of a specified file. The file name is passed as a command line argument. Save the following script as
file_reader.sh
.Use this script to read
/etc/passwd
.The Internal Field Separator (IFS) is used to recognize word boundaries. The default value for IFS consists of whitespace characters. Whitespace characters are space, tab and newline. Add the following script to the file
ifs_test.sh
.Run the script.
The default value for the IFS can be changed. Modify
ifs_test.sh
to contain the following.Let’s do this on a larger scale. Read /etc/passwd word by word while using
:
as the IFS. Save the following asifs_word.sh
.Run the script specifying
/etc/passwd
as an argument.Task 3 - Jump directories
Sometimes it is difficult to navigate directories with the possibly infinite number of parent directories we need to provide. For example
cd ../../../../../
. Let’s create a script that will help us jump to a specified directory without executingcd ../
.jump_dir.sh
.Exercise 3 - Hash tables and more bash usage
Task 1 - Hash tables in bash
A dictionary, or a hashmap, or an associative array is a data structure used to store a collection of things. A dictionary consists of a collection of key-value pairs. Each key is mapped to its associated value.
declare
statement with the-A
option which means associative array. For example:newDictionary
. The following syntax can be used to add key-value pairs to the dictionary:$
with braces{}
to the dictionary variable we defined.newDictionary[1]
, we do the following: Retrieve the value of they key[1]
to verify the update.unset
command to remove a key-value pair from the dictionary. Verify that the pair has been removed from the dictionary.for
loop. An example is shown in the script below: You should get an output similar to the following when you run the script:Task 2 - Use sed in bash
sed_bash.sh
. Run the bash script and analyse the output. There is no HTML tag in the results.Task 3 - Execute Python commands in bash
-c
. An example is shown with the command below: The following bash script executes the python command above.Exercise 4 - Debugging bash scripts
Task 1 - Command exit code
You can verify whether a bash command executed successfully by viewing the exit status code of the command. The exit status of the previously executed command is stored in the
$?
variable. A successful command returns a0
, while an unsuccessful one returns a non-zero value that usually can be interpreted as an error code.$ ls -lah
.$ echo "$?"
.$ ls -lah /directorythatdoesnotexist
$ echo "$?"
again to view the exit status. You should get a value that is not0
.Task 2 - Using
set -xe
When there is an error that stops the execution of a bash script, the bash interpreter usually displays the line number that triggered the error. However, in some cases, it might be necessary to trace the flow of execution of the script. This provides more insight into the conditions that are met, and the state of the loops.
set
command is used to set or unset shell options or positional parameters.set -e
option to exit the bash script if any command or statement generates a non-zero exit code. This is defined at the start of the script and it applies globally to all commands in the script.set -x
option to display commands and arguments before they are executed. With this option, we can see every line of command that is executing in the script.set -e
option and-x
options can be combined to become useful debugging tools. The option-e
exits the script as soon as an error is encountered, and the option-x
shows the command that was running when the error was encountered.loop_debug.sh
and add the following to it:-e
and run the script again. Did you notice any difference? Why is it different?set -xe
to see how the program executes without these options.Questions to answer
Write a bash script that displays the following details of the logged-in user from the environment variables:
ifconfig
orip
command. Save the IP address to theipaddress
variable and display it as output.Sample output:
Backups are important in system administration. Create a script that will backup your home directory.
tar.gz
./var/backups/
home_backup_month_day_year_hour_minute_second.tar.gz
.For example
home_backup_Feb_18_2023_02_30_02.tar.gz
Write a bash script that checks various artifacts on the system. The script mainly checks for system information, and OS components. Your script should do the following:
*
to them in the output).Bonus