0% found this document useful (0 votes)
9 views24 pages

Decision Making

The document discusses different flow control mechanisms in BASH including if/else, case, select, for, while and until. It provides details on using if conditionals and testing conditions. It also describes exit statuses and using commands together in conditions.

Uploaded by

Varun
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views24 pages

Decision Making

The document discusses different flow control mechanisms in BASH including if/else, case, select, for, while and until. It provides details on using if conditionals and testing conditions. It also describes exit statuses and using commands together in conditions.

Uploaded by

Varun
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 24

Decision Making

CONDITIONS AND LOOPS


The need for flow control
Any programming or scripting language needs some way to make branches for code execution.
That is, paths that the code will follow if certain conditions are met, or unmet.
There is also the crucial need to execute a command or a set of commands multiple times: for a
specific number of times, as long as a condition is true, or even indefinitely.
For those reasons, BASH provides a number of flow control mechanisms that address the above
requirements and more. Namely:
◦ If/else: execute a number of commands if some condition is true. The elif and else keywords provide
even more branching.
◦ case: execute one of several available commands from a list depending on the value of a certain
variable.
◦ select: let the user choose one of a list of available options from a menu
◦ for: run a command or a set of commands for a specific number of times.
◦ while: run a command or a set of commands as long as a certain condition is met (true).
◦ until: run a command or a set of commands until a certain condition is met
The if conditional
It is used to direct code path depending on whether or not a condition is true.
The syntax of if in BASH is as follows:
if condition; then
code
elif condition
code
else
code
fi
Further branching can be made (based on other conditions) with the elif (short for else if). The
else statement is used as a “catch all” solution. That is, when all the above conditions are not met,
execute this code.
Both elif and else are optional in the if conditionals. You can use just use if and fi to test for one
condition.
How if tests for the condition
In popular programming language like C#, Java, and others, if statements test for a condition by
evaluating an equation and acting according to the Boolean result (true or false). In BASH, things
are little different.
The if condition here tests for the exit status of commands. Exit status is an old UNIX concept.
Any command (shell script, or binary command) that runs must have an exit status. It is a
number that signifies whether the result of the command was a success (conventionally 0), or a
failure (non-zero, from 1 to 255 possible numbers that represent various possible errors). An exit
status is stored in the special built-in variable $?
Accordingly, the if checks for the execution result of one or more commands, if the exit status is
0, it executes some code, otherwise (elif or else) some other code gets executed.
For example, the script written in one of the previous section’s labs, the user had to enter the
filename (websites.txt) so that the script can return the highest ranking websites. We used
shell substation to ensure that the user entered something as the first command line argument
(${1:?Please enter the filename}). But what if the user mistyped the filename?
Shell substitution will not detect such a situation.
LAB: Let’s enhance highest.sh
In order to make the script ensure that the first command line argument is a filename, we can choose
a command that will only work on a filename (exit status 0), and will fail otherwise (exit status non-
zero).
A good candidate for this is the file command. So we could enhance that part of the highest.sh
script as follows:
filename=${1:?Please enter a filename}
count=${2:-5}
if ls $filename > /dev/null 2>&1; then
sort -n $filename | tail -$count
else
echo "Please enter a valid filename"
exit 1
fi
Note that redirected the output of ls (whether the filename or any error messages) to /dev/null
to prevent it from being printed to the screen.
If the result of ls filename is true, continue with the script. Otherwise, print a friendly message
to the screen and exit the script. Notice the use of the exit command followed by a number to
return an exit status of the script so that it can be used in – perhaps – a calling script
exit vs return
The exit command is used to terminate the whole script, with an optional numerical status.
The return command, on the other hand, is used inside functions (or scripts run with the source command) to
provide a value from that function, or an exit status. For example, the highest.sh could be written as follows:
filename=${1:?Please enter a filename}
count=${2:-5}
function getWebsites(){
if ls $filename > /dev/null 2>&1; then
echo $(sort -n $filename | tail -$count)
else
echo "Please enter a valid filename"
return 1
fi
}
getWebsites
Here we encapsulated the script logic to be inside a function, which is then called. Note that we used the return
command without a numerical value, which will make the function return the exit status of the last command
run.
Mixing exit statuses
You can combine exit statuses of two or more commands in an if condition statement, as well as in the interactive-mode, to execute code
accordingly.
You can use the && symbol, which corresponds to “and” and || which corresponds to “to”.
For example:
/etc/init.d/httpd start && tail –f tail /var/log/httpd/access_log
This will issue the command to start the Apache webserver, and, if that goes right, monitor the access log file.
Another example may be:
touch test 2> /dev/null || echo "No write permission"
Here we are suppressing the error message of the touch command and replacing it with our own by using an OR operation. If the user has
write permissions, the touch command will work normally. Otherwise, our error message is displayed.
The above operations can also be used with the if conditional statements to calculate the appropriate exit status. For example, we may
want to restrict running this script to the root user:
if ls $filename > /dev/null 2>&1 && cat /etc/shadow > /dev/null 2>&1; then
sort -n $filename | tail -$count
else
echo "Sorry only root can do this“
exit 1
fi
Here we are adding another condition to the one where we ensure that $filename is a valid file: we are challenging the user to run a
command that can only be run as root. If the command fails (exit status non-zero), the whole script will exit even if the $filename is valid
More elegant ways of testing conditions
The if statement only works with the exit status of commands passed to it. however, this does
not mean that we cannot use it the way it is used in other programming languages. For example,
what if we want to test if a file size is greater than a number? Or that the file name equals a
specific text?
For those requirements, BASH provides [ … ] and [[ … ]]construct. It can be used test a lot of file
attributes like whether or not it exists, its type, its permissions, modification date, among other
things.
The difference between [ … ] and [[ … ]] is that the later can do command expansion (using the
$() notation).
The construct is used as [ condition ]. Notice the required spaces before and after the condition
statement.
All what this construct does is that it performs an operation that would test for something (using
one of its various keywords), and returns the exit status of 0 if the test succeeds.
Testing for strings
The following is the operators used to compare two strings.
Operator Usage Example to yield true
= Both strings are equal [ "test" = "test" ]
!= Strings are not equal [ "exam" != "test" ]
< First string is less than the second (numerically or alphabetically) [[ "a" < "b" ]]
> First string is greater than the second (numerically or alphabetically) [[ "b" > "a" ]]
-n The string is not null [ -n "johndoe" ]
-z The string is null [ -z "" ]

Note that you can use the ! sign before condition to negate it. That is, exit with 0 if the condition
is not met. For example ! [[ "b" > "a" ]] is the same as [[ "b" < "a" ]]
LAB: Further enhancing highest.sh
So far we have tested that a string representing the filename is passed to the script, and that
this string represents the path to an actual, existing file. But if that file was empty? You may
want to print some friendly message to the user instead of just a blank line. This could be done
as follows:
filename=${1:?Please enter a filename}
count=${2:-5}
if ls $1 > /dev/null 2>&1 && [[ -n $(cat $filename) ]]; then
sort -n $filename | tail -$count
else
echo "Invalid filename or file is empty"
exit 1
fi
Here we added an extra condition, and using the && operator, we are forcing the script to
process the file only if the two conditions are met. The second condition ensures that the output
of cat $filename is not null, which means that the file does have some text to process. Notice
that we used the double square brackets to allow expansion.
Testing for file attributes
The following are the most commonly used operators to check for different file attributes:
Operator Usage Example
-a The file exists [ -a /etc/passwd ]
-d The file exists and it represents a directory [ -d /var/www ]
-f The file exists and it represents a regular file [ -f /etc/passwd ]
-s The file exists and is not empty [ -s /etc/passwd ]
-w The file is writable (by your user account) [ -w websites.txt ]
-x The file is executable (by your user account) [-x highest.sh ]
-N The file has been modified since it was last read [-N websites.txt ]
-O The file is owned by your account [ -O websites.txt ]
-G The file is owned by your group [ -G websites.txt ]
-nt The first file is newer than the second one [ file1 –nt file2 ]
-ot The first file is older than the second one [ file1 –ot file2 ]
LAB: Applying string comparison to
highest.sh
So far we have used a complex method to test whether or not the filename passed to the script
represents an existing filename: we used the exit status of the ls command. Additionally, we
used the output of the cat command to ensure that it is not empty. But string operators provide
a more elegant way of achieving the same result:
filename=${1:?Please enter a filename}
count=${2:-5}
if [ -s $filename ]; then
sort -n $filename | tail -$count
else
echo "Invalid filename or file is empty"
exit 1
fi
The –s here ensures that the file exists and that it contains text before allowing the sort
command to process it.
Testing for integers
The following are the operators used for integers:

Operator Usage Example that yields true


-lt Less than [ 0 –lt 1 ]
-le Less than or equal [ 5 –le 5 ]
-eq Equal [ 5 –eq 5 ]
-gt Greater than [ 5 –gt 0 ]
-ge Greater than or equal [ 5 –gt 5 ]
-ne Not equal to [ 5 –ne 0 ]
LAB: Avoiding negative values in
highest.sh
Back to our script highest.sh, which was used to generate a list of the highest ranking websites from a
text file. The user can optionally enter a digit to define the number of websites to be returned.
Internally, this digit is passed to the tail command. What is the user (by mistake or intentionally)
entered a negative value? This would break the script. Lets use integer testing operators to prevent
that from happening:
filename=${1:?Please enter a filename}
count=${2:-5}
if [ $count -lt 1 ]; then
echo "Please enter a valid value for count"
exit 2
fi
if [ -s $filename ]; then
sort -n $filename | tail -$count
else
echo "Invalid filename or file is empty"
exit 1
fi
If the user entered a numerical second argument to the script, it must be greater than 1 so that the
script can generate any results.
The for loop
It is the most common type of loops in BASH
It is used to execute a command or a set of commands a number of times.
However, BASH differs a little bit in its implementation of the for loop than other programming
languages like Java for example. In BASH, you specify a fixed list of values over which the for loops
iterates.
This makes for loops very powerful when applied on lists. For example, you can loop over a list of files
in a directory or on the command-line arguments.
The syntax of a for loop is as follows:
for variable in list; do
#code that can use $variable
done
The list is a list of names separated by the IFS variable value (IFS defaults to a space of TAB character)
LAB: usernames and home directories from /etc/passwd
The /etc/passwd file contains information about the users of the system. But since it is not commented,
sometimes you need to remember which field represented which value. The following script will print only the
username and the home directories for all users in /etc/passwd file:
IFS=$'\n'
for user in $(cat /etc/passwd); do
username=$(echo $user | cut -d : -f 1)
home=$(echo $user | cut -d : -f 6)
echo "Username:" $username "Home directory: " $home
done
First we initialize the IFS special variable to the newline character; as it normally defaults to TAB or space
characters. The for loop uses the value of this variable to determine the character that separates the items on
which it iterates.
Then we use the $user variable to hold each item in the list (one line), and we hand the loop the list (the output
of cat /etc/passwd)
The rest is just using the cut command to filter the fields of /etc/passwd line by the colon sign ":" and display only
the first and the seventh columns, which correspond to the username and the home directory of the user.
LAB: use highest.sh on multiple files
To demonstrate the use of for loops on command-line arguments, we can modify the highest.sh script so that the user can enter more
than one text file as command-line arguments, and the script will concatenate them before processing the content:
count=5
for f in "$@"; do
if ! [ -s $f ]; then
echo "$f is an invalid file or is empty"
exit 1
fi
done
container=$(cat "$@")
echo "$container" | sort -n | tail -$count
The for loop here is used to ensure that all files entered on the command line are valid (existing and non-empty). For the sake of
simplicity, we disabled the count option that the user could enter as a second command line argument and fixed it to 5 websites.
The cat command is applied to the $@, which prints the command-line arguments as one lone string, to concatenate their content. The
output is stored in $container variable.
Finally the sort command can be applied to the content of the $container variable.
The script can be called like this:
./highest.sh websites1.txt websites2.txt websites3.txt
The case statement
The case statement in BASH lets you compare strings to patterns. Wildcard characters can be
used in the operation.
The syntax is follows:

case expression
in
pattern1 )
code ;;
pattern2 )
code ;;
esac
LAB: change highest.sh to allow getting
highest and lowest ranking websites
Using the case statement, we need to add an option that the user can choose whether to get the highest or the lowest ranking websites. Again we
are hardcoding the count variable to be 5 for the sake of simplicity:
filename=${1:?Please enter a filename}
count=5
order=${2:--h}
if [ -s $filename ]; then
case $order
in
*-h )
sort -n $filename | tail -$count ;;
*-l )
sort -rn $filename | tail -$count ;;
* )
echo "Please enter –l or –h"
esac
else
echo "Invalid filename or file is empty"
exit 1
fi
The change made here is introducing the $order variable. It will – optionally – take the second command line argument as either –h (highest
ranking), or –l (lowest ranking), with the default set as –h. The count is set to 5.
Then comes the case statement; the sort command is run normally when $order is –h, and run with the inverse switch (-r), which would make it sort
the list descending.
The use of patterns here is optional. With patterns, the script will interpret any string ending in –h or –l as a valid argument. Notice the use of the
"catch all" statement at the end of case block *) which will get invoked when the user inputs an invalid option.
The select statement
It is only available in BASH and Korn shells.
It allows you to make simple menus from which the user can choose an option, this option will trigger
a configured action.
It has a syntax similar to the for loop:
select variable
[in
set
]
do
code that can reference $variable
done
If you omitted the in set part, it will default to "$@" variable.
When the user selects an option from the menu, that choice is stored in $variable and the number
itself is stored in the built-in variable $REPLY.
LAB: create a simple user menu
To demonstrate select functionality, we are going to create a very simple menu that will allow the user to print his/her login id and to
change the current password:
PS3="Your choice? "
select s in "Print loginid" "Change password" "Exit"; do
case $s in
*loginid )
echo "Your login ID is " $(id -u)
break ;;
*password )
passwd
break ;;
Exit )
break ;;
* )
echo "Invalid selection"
esac
done
We use the keyword break to terminate the menu.
Note that we used the case statement to validate the choice made by the user. The asterisk * is for pattern matching.
If the user pressed RETURN without making a selection, the menu will be re-printed. The only way to exit the menu is to press 3 or type
CTRL-C
The while and until loops
While and until loops are used to execute one or more code statements as long as a condition is
met.
The syntax of while and until is as follows:

while/until condition; do
code
done
The condition is the same one used for the if statement: a command that has the exit status of
0.
The difference between while and until is that the while loop runs as long as the
condition is true, whereas the until loop runs as long as the condition is false.
LAB: write to a file until a certain size
Almost all applications write their events and other data to log files. But those files can quickly get
large so that they fill your space. A good practice is to rotate log files once they reach a certain limit
so that they can be archived. In this lab we'll simulate a log file that is constantly filled with random
data, then use the while loop to monitor its size and rotate it:
timestamp=$(date +%Y%m%d-%H%M%S)
while [[ $(du –k data.log | awk '{print $1}') -gt 100 ]]; do
cp -p data.log data.log.$timestamp
> data.log
done
As soon as the file reaches 100 KB, it will be copied to a new file with a timestamp attached to denote
when the rotation happened, and the file itself is truncated.
The while loop works as long as the condition is true (file size is greater than or equal 100 KB). This
script can be run in the background to ensure proper rotation.
LAB: create a “retry ssh” script
Lots of times you reboot a machine and keep on waiting till it is up so that you can connect to it
using SSH. Every few seconds you try the command until the password prompt is shown. Using
the until loop, we can automate this process as follows:

host=${1:?Please enter the hostname/IP address of the machine}


until ssh $host; do
echo "Machine not ready… retrying"
done
The script takes the machine name or IP address as the first argument, then it tries to connect to
it using ssh.
As long as the exit status of the ssh command is not zero (unsuccessful connection), the friendly
message will keep on showing until the command succeeds and the loop, thus, exits

You might also like