Flow Control - Part 2
Hold on to your hats. This lesson is going to be a big one!
More Branching
In the previous lesson on flow control we
learned about the if command and how it is used to
alter program flow based on a command's exit status. In programming terms, this
type of program flow is called branching because it is like traversing a
tree. We come to a fork in the tree and the evaluation of a condition
determines which branch we take.
There is a second and more complex kind of branching called a case. A case is multiple-choice branch. Unlike the simple branch, where we take one of two possible paths, a case supports several possible outcomes based on the evaluation of a value.
We can construct this type of branch with multiple if statements. In the example below, we evaluate some input
from the user:
read -p "Enter a number between 1 and 3 inclusive > " character
if [ "$character" = "1" ]; then
echo "You entered one."
elif [ "$character" = "2" ]; then
echo "You entered two."
elif [ "$character" = "3" ]; then
echo "You entered three."
else
echo "You did not enter a number between 1 and 3."
fiNot very pretty.
Fortunately, the shell provides a more elegant solution to this problem. It
provides a built-in command called case, which can be used to construct
an equivalent program:
read -p "Enter a number between 1 and 3 inclusive > " character
case $character in
1 ) echo "You entered one."
;;
2 ) echo "You entered two."
;;
3 ) echo "You entered three."
;;
* ) echo "You did not enter a number between 1 and 3."
esacThe case command has the following form:
case word in
patterns ) commands ;;
esaccase selectively executes statements if
word matches a pattern. We can have any number of patterns and
statements. Patterns can be literal text or wildcards. We can have multiple
patterns separated by the "|" character. Here is a more advanced
example to show how this works:
read -p "Type a digit or a letter > " character
case $character in
# Check for letters
[[:lower:]] | [[:upper:]] ) echo "You typed the letter $character"
;;
# Check for digits
[0-9] ) echo "You typed the digit $character"
;;
# Check for anything else
* ) echo "You did not type a letter or a digit"
esacNotice the special pattern "*". This pattern will match
anything, so it is used to catch cases that did not match previous patterns.
Inclusion of this pattern at the end is wise, as it can be used to detect
invalid input.
Loops
The final type of program flow control we will discuss is called
looping. Looping is repeatedly executing a section of a program based on
the exit status of a command. The shell provides three commands for looping:
while, until and for. We are going to cover while and until in this lesson and
for in a upcoming lesson.
The while command causes a block of code to be
executed over and over, as long as the exit status of a specified command is
true. Here is a simple example of a program that counts from zero to nine:
while [ "$number" -lt 10 ]; do
echo "Number = $number"
number=$((number + 1))
doneOn line 3, we create a variable called number and initialize
its value to 0. Next, we start the while loop. As we
can see, we have specified a command that tests the value of
number. In our example, we test to see if number has
a value less than 10.
Notice the word do on line 4 and the word done on
line 7. These enclose the block of code that will be repeated as long as the
exit status remains zero.
In most cases, the block of code that repeats must do something that will eventually change the exit status, otherwise we will have what is called an endless loop; that is, a loop that never ends.
In the example, the repeating block of code outputs the value of
number (the echo command on line 5) and
increments number by one on line 6. Each time the block of code is
completed, the test command's exit status is evaluated again. After the tenth
iteration of the loop, number has been incremented ten times and
the test command will terminate with a non-zero exit
status. At that point, the program flow resumes with the statement following
the word done. Since done is the last line of our
example, the program ends.
The until command works exactly the same way,
except the block of code is repeated as long as the specified command's exit
status is false. In the example below, notice how the expression given to the
test command has been changed from the while example to achieve the same result:
until [ "$number" -ge 10 ]; do
echo "Number = $number"
number=$((number + 1))
doneBuilding a Menu
A common user interface for text-based programs is a menu. A menu is a list of choices from which the user can pick.
In the example below, we use our new knowledge of loops and cases to build a simple menu driven application:
until [ "$selection" = "0" ]; do
echo "
PROGRAM MENU
1 - Display free disk space
2 - Display free memory
0 - exit program
"
echo -n "Enter selection: "
read selection
echo ""
case $selection in
1 ) df ;;
2 ) free ;;
0 ) exit ;;
* ) echo "Please enter 1, 2, or 0"
esac
doneThe purpose of the until loop in this program is
to re-display the menu each time a selection has been completed. The loop will
continue until selection is equal to 0, the "exit" choice. Notice how we defend
against entries from the user that are not valid choices.
To make this program better looking when it runs, we can enhance it by adding a function that asks the user to press the Enter key after each selection has been completed, and clears the screen before the menu is displayed again. Here is the enhanced example:
echo -en "\nPress Enter to continue"
read
clear
}
selection=
until [ "$selection" = "0" ]; do
echo "
PROGRAM MENU
1 - display free disk space
2 - display free memory
0 - exit program
"
echo -n "Enter selection: "
read selection
echo ""
case $selection in
1 ) df ; press_enter ;;
2 ) free ; press_enter ;;
0 ) exit ;;
* ) echo "Please enter 1, 2, or 0"; press_enter
esac
done