Main Body

5 Control Structures – Part 1 – branching

Lester Hiraki

In order to support control structures (branching, looping, etc.), all computer languages need some method of evaluating a condition.  In Unix, the command to evaluate a condition is the test command.  The test command is often at the heart of most control structures in Unix.

 

test

What does the test command do?  It evaluates a condition and sets the special shell variable $?, the return code.  Much to the confusion of new users, the test command is silent in that it does not print anything to standard output.  Thus when running the test command, it appears as if to do nothing.  To check the return code, one may simply print its value with an echo statement.

Example 1:  Check if a file is readable.

$ test -r myfile
$ echo $?
0

 

How does one interpret the return code?  

Unix convention:

0 (zero)

means TRUE

not 0 (not zero)

means FALSE

 

Example 2:  Compare if two strings are equal.

$ test "this" = "that"
$ echo $?
1

 

Synonym to the test command:  [

For reasons of readability, many programmers with use the synonym or abbreviation for the test command which is the left square bracket: [ .  In all examples above, replace the word test with the left square bracket.  Note that a matching right square bracket needs to be added for syntactic reasons.  As with all Unix commands, spaces are a big deal and a space is required after the left square bracket and before the right square bracket.

 

Example 3: Synonym to the test command [

$ [ -r myfile ] # note the space after the left bracket
$ echo $?
0 

 

Control Structures

if

The if statement in Unix is the basic two-way branch.

 

Syntax

if unix_statements
then
   actions_true
else  # optional
   actions_false
fi

How does the if statement work?  Here is the sequence of operations.  The if statement will:

  1. Run all the unix_statements.  
  2. Check the return code ($?) of the last statement in the list (just prior to the keyword “then”.
  3. If the return code is true. the “then” clause is executed (
    actions_true).  If the return code is false, the “else” clause is executed (actions_false).  The else clause is optional (can be left out if not needed).

Example of if

#!/bin/bash
comfort=20
temperature=18
if echo The current temperature is $temperature
   echo Temperature for comparison is $comfort
   [ $temperature -lt $comfort ]
then
  echo It is cold.
else
  echo It is warm.
fi 

Nested conditions are supported in Unix.  Any statement in the “then” or “else” clause can itself be another if statement.

elif

Although the if statement is primarily a two-way branch (e.g. true or false), a multi-way branch (e.g. red, yellow, green) can be coded using a set of nested if statements.  Some computer languages support an “else-if”-type clause; Unix is one of them.

There is an “else-if” clause called “elif” which requires a statement just like the if clause.

Example: elif

if [ $temperature -lt $comfort ]
then
  echo It is cold.
elif [ $temperature -eq $comfort ]
then
  echo It is perfect.
else
  echo It is warm.
fi
 

Note that the “elif” clause is actually a clause of the main “if” and not a nested if statement.  Thus, there is only one “fi” (end if) required for each opening “if” regardless of how many “elif” clauses there are.

case

Many computer languages support a multi-way branch. Unix is included as one of them.

Simplified syntax:

 

case $variable 
in
  val1) action1;;
  val2) action2;;
  *) default action;;
esac 

The keywords are case, in, and esac.  The double semi-colon is a syntactic requirement to separate the inner clauses of the case statement.

 

 

 

Example:  Flexible command line processing.

Allow your user to run your script in various ways.  Accommodate all of the following invocations.

$ ./myscript # user omits filename; give second chance
$ ./myscript chapter3 # preferred syntax; just proceed
$ ./myscript chapter3 chapter5 # multiple arguments not supported; 
inform user

Place this code snippet at the beginning of your script like this:
$ cat myscript
case $# in
  0) echo Enter file name:
           read arg1;;
  1) arg1=$1;;
  *) echo invalid number of arguments
     echo "Syntax: $0 filename"
     exit 1;;
esac
# rest of program continues after esac
$

 

General syntax: 

case match_string_expr in
  match_pattern) action1;;
  match_pattern) action2;;
  ...
esac

 

Example:  Demonstrate pattern matching use in case statement.  Print a message about the length of the current month.

lhiraki@metis:~/test$ cat case_month
case $(date '+%m') in
01|03|05|07|08|10|12)
     echo This is a long month;;
04|06|09|11)
     echo This is a short month;;
02)
     echo This is the shortest month;;
*)
     echo Something wrong with date command;;
esac

# Example run in September

lhiraki@metis:~/test$ ./case_month
This is a short month
lhiraki@metis:~/test$

The date command is called with an option to return the numerical value of the month (e.g. Jan=01, Feb=02, etc.).  Depending on the month, a message is printed regarding the length of the month.  Months with the same number of days are grouped together using a pattern with the OR (vertical bar) syntax.

 

Defensive programming

Well the previous example is rather trivial and the date command has been well tested over the years, it is considered good practice to always have a default clause even if you think you have covered all possible conditions.

Summary

Entry arrow points to circle labled case; multiple arrows leave case circle to rectangles each with a separate action; each action has one arrow leaving which are all collected to a common exit.
Flow of case statement: one entry point, only one action chosen.

 

 The match_string_expr is matched against each match_pattern in the order coded.  At the first match, the corresponding action is taken.  After one action is completed, the case statement terminates and execution continues after the esac (end case).  There is no “fall-through”.  The case statement will not execute multiple actions.

shift

When processing multiple command-line arguments, it may be necessary to manipulate them to facilitate processing.

The shift command moves all command-line arguments one position to the left.  For example the second command line argument is moved into the first position; the third command line argument is moved into the second position, and so forth.

 

Example of  shift command

lhiraki@thebe:~/test$ cat shifter

#!/bin/bash
echo The 1st arg is $1
echo The 2nd arg is $2
shift
echo The 2nd arg is $1
shift
echo The 3rd arg is $1 

Execution results in:

lhiraki@thebe:~/test$ ./shifter apple pear grape
The 1st arg is apple
The 2nd arg is pear
The 2nd arg is pear
The 3rd arg is grape
lhiraki@thebe:~/test$

 

The exit command

The exit command does two things:

  1. It terminates the current shell (or script), returning control to the calling shell, if any.
  2. It sets the return code ($?) for your script.

Example – exit command usage

The trivial but illustrative script exit_example shows the exit command setting return code for the script to 3 and then terminating the script.  Control returns to the calling program, in this case just back to the operating system prompt.

$ cat exit_example
#!/bin/bash
exit 3
echo This line never executed.

$ ./exit_example
$ echo $?
3
$

Note that one must inspect the return code ($?) immediately after running exit_example.  The return code is updated (overwritten) by each Unix command executed.

The exit command is typically used to terminate a script midway through often due to an errror condition. The other primary use of the exit command is for a sub-script to communicate information back to the calling script.

 

Example – parent/child script relation

parent

child

./child 
ret_val=$?
case $ret_val
...
   exit 2
...
exit 0

Note: parent-child relationship can be used for team project where multiple people can work on the same file at the same time.

 

Shebang line

To specify which interpreter Unix should use when executing your script, as the first line of the file place the path to the interpreter after “#!”.  While the number sign character (#) normally introduces a comment, when used with the exclamation mark at the beginning of a file, Unix will load the interpreter specified in the path to run the rest of the script file.

This is especially important to make your script portable in Unix environments.  If you write your script in bash, and you give your script for someone else to use who works in a c-shell or Korn shell environment, your script may not work properly.  To ensure that the script is run under bash, you must specify the shebang line.

Careful:  The shebang line must be the first line of the file, not just the first line of text, or the first line of code.  A common mistake is to have a blank line as the first line, or some comments above the shebang line.  Unix does not look beyond the first line of the file in order to identify the expected interpreter.

Right

Wrong

Wrong

#!/bin/bash
#comments
#comments
code begins here        
#comments
#comments
#!/bin/bash
code begins here
#!/bin/bash
code begins here

 

License

Icon for the Creative Commons Attribution-NonCommercial 4.0 International License

Productivity in Common Operating Systems Copyright © 2022 by Lester Hiraki is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License, except where otherwise noted.

Share This Book