Main Body

6 Control Structures – Part 2 – Loops

Eventually you will need to repeat actions.  A practical way to repeat actions without repeating code is achieved with the construct of a loop.

Computer Science Loop Concepts

The construct of a loop allows for the repetition of actions without repeating code.  

General Loop (Concept)

The most general flow of control is illustrated in the flowchart below (General).  Observe that there can be a set of actions (Block 1) which is performed regardless on entry into the loop.  Then a conditional check is made to determine continuation of the loop.  If satisfied, further actions within the loop (Block 2) will be performed.  

 

Not all computer languages support the General loop construct (Unix
does support a General loop).  If a computer language does not support the General loop construct, it will usually support one or more of the specific cases of the General loop.  For example, C Language supports both a While and a Do-While construct but not the General loop construct.

While Loop (Concept)

The While Loop, as a concept, is a special case of a General Loop which has no actions for Block 1.  In this case, the condition is checked as a first action on entry into the loop construct.  See flowchart below (While).

Do-While Loop (Concept)

The Do-While Loop, as a concept, is a special case of a General Loop which has no actions for Block 2.  In this case, the Block 1 code is executed on entry into the loop construct, and the condition is checked at the end of the loop construct.  See flowchart below (Do-While).

General

While

Do-While


General loop has arrow to block 1 before decision and block 2 after decision; decision has 2 arrows out with true path leading to block 2 and false path leaving loop; arrow from block 2 loops back to block 1.


While has arrow to decision (no block 1) with arrow to block 2 then arrow from block 2 loops back to decision.


Do-while has has arrow to block 1 before decision (no block2). True output of decision box loops back to block 1.

 

Loops in Unix

There are three commands in Unix to support loops.

  • while
  • until        
  • for

while command

The while command in Unix implements the General Loop construct discussed earlier.

 

general syntax

relation to flow chart

while list1
do
  list2
done
while block1
      check
do
   block 2
done

Unix runs the list1 and looks at the return code ($?) of the last command in list.  (If there’s only one command in the list1, then that’s the last command.)  If the return code of this last command is TRUE, then list2 (loop body) is run.  Once the keyword “done” is reached, control returns to re-run list1 and the return code of the last command in list1 is checked.   If the return code of this last command is FALSE, then the while loop terminates, and control continues after the keyword “done”.

 

E.g. 1: Count from 1 to 12

month=1                                
while echo Checking limit against month ${month}
  [ ${month} -le 12 ]   # The test is the last command in list1
do
   echo Performing action for month ${month}
   month=$(( ${month} + 1))
done 
echo Value of month outside loop is ${month}

Notes and observations

  1. The last command of list1 is a test command using the left bracket synonym.
  2. The -le is a test command option for the numeric comparison less than or equal to.
  3. As Unix program variables hold strings, to perform arithmetic operations, one must use the $(( .. )) syntax with dollar sign and double parentheses.
  4. The formal spelling of Unix variables requires enclosure in set braces { }.  Where there is no ambiguity, it is common practice in Unix to omit the set braces.
  5. Try running this code and see that the value of month never exceeds 12 in the line “echo Performing action …”.

 

Preamble: Redirecting input from a file

$ cat novel
It was a
dark and
stormy
$ read oneline < novel
$ echo $oneline
It was a
$ read oneline < novel
$ echo $oneline
It was a
$

 

Be reminded that redirection operators in Unix (<, >, etc.) apply only to the current command.  Once the command is over, redirection is restored to the default configuration (STDIN from keyboard, STDOUT to screen, etc.).   Thus, observe that each time the read command is run, redirection is applied anew, and the first line of the file is read in.

Eg 2a: How to read every line of a file (a non-functional example)

The objective is to read a file one line at a time.  Here is a newbie’s first attempt.

 

$ cat badwhile
while read wholeline < novel
do
        something $wholeline         #e.g. echo $wholeline
done
$ ./badwhile
It was a
It was a
It was a
It was a
...


What happened?  The redirection operator applies to the read command.  Each time the read command is run, redirection is applied anew and only the first line of the file is ever read.

 

 

How can one fix this problem?

Eg 2b: How to read every line of a file? (a functional example)

To have redirection apply to the entire duration of the while loop and not just the read command, it is necessary to establish redirection and associate it with the while loop.

Here is a template which you can use to read lines from a file one-at-a-time.

while read wholeline
do
        something $wholeline  #e.g. echo $wholeline
done < inputfile

To make this work, one must position the redirection operator after the command to which it should be applied.  The correct position is after the while command, in particular the keyword done.  Unix is smart enough to look ahead for redirection when it starts executing the while command.  Any commands within the while command which draw from STDIN (e.g. read) will draw from the redirected file. (If you are having difficulty understanding redirection, refer to the chapter on Redirection and Pipes.)

 

Eg 3: How to process multiple command line arguments (Method 1)

Recall that the cat command will accept any number of command line arguments like this:

cat ch1 ch2 ch3 ...

Let’s say that you want to write your own Unix script that will accept any number of command line arguments:

mycmd parm1 parm2 ...

The key requirement here is that you will not know beforehand how many command-line arguments the user will supply when the user runs your program.  Your program must be flexible enough to handle zero or more command line arguments.

Here is a template of code to solve this problem:

while [ $1 ]
do
   someprocess $1
   shift
done

Explanatory notes:

  1. The last command in the while list is a test command (using the left square bracket synonym).
  2. With no specific test operator (e.g. -r, etc.), the default behaviour of the test command is to check if the string is null.  The return code ($?) is TRUE for a non-null string (something is there), and FALSE for a null string.
  3. The shift command slides all command line parameters to the left, in this case moving the next unprocessed command line argument into the $1 position.  (The shift command also has the side-effect of decrementing the $# variable.)
  4. Once there are no more command line arguments, the loop terminates.  Note that if there were no command line arguments to begin with ($1 is NULL), the loop immediately terminates without entry into the body.  

 

Exercise: Password entry loop

Write a bash script which will continually prompt a user to enter a password matching the control flow in the example below.  If the user enters the correct password, grant access.  If the user enters the wrong password, give the user another try (unlimited).  Avoid code duplication.

Sample dialog:

$ ./whilecmdlist
What is the password? happy
--- Sorry, that is not the right password.
What is the password? access
--- Sorry, that is not the right password.
What is the password? Strawberry
Welcome to the system.
$

until command

The until command in Unix implements the General Loop construct discussed earlier.

The syntax is identical to the while command with the only difference that the logic of the conditional test is reversed.  If the last command in list1 is TRUE, the loop terminates.

Here’s the same explanation for the until command:

Unix runs the list1 and looks at the return code ($?) of the last command in list.  (If there’s only one command in the list1, then that’s the last command.)  If the return code of this last command is FALSE, then list2 (loop body) is run.  Once the keyword “done” is reached, control returns to re-run list1 and the return code of the last command in list1 is checked.   If the return code of this last command is TRUE, then the until loop terminates, and control continues after the keyword “done”.

for command

The for command in Unix operates quite differently than what is common in other computer languages like BASIC or Pascal.  In particular, it is not a counted loop, rather one should think “iterative substitution”.

Syntax: 

for name in word ...
do
   list
done

The list of words is expanded to create a list of items.  Each of these items is substituted into the variable name one-at-a-time and the list is run.

 

E.g. 1: Multiple file rename problem

Consider the problem of adding a suffix to several files.

Those familiar with a DOS or Windows Power Shell environment could use a command like this:

ren assig* assig*.bak

Unfortunately a similar command does not work in Unix:

mv assig* assig*.backup

Nevertheless, this problem can be solved with the simple use of a for command:

for filevar in assig*
do
   mv ${filevar} ${filevar}.backup
done 

Explanatory notes

  1. The word list is the wildcard file specification assig*.  On expansion, for the purposes of this example, assume that the result is three matching files: assig1, assig2, assig3.
  2. Each of these words is substituted one-at-a-time in the loop variable filevar, and the body of the loop is run (code between do and done).
  3. When the body of the loop is run with the variable substitutions, the effective commands generated are:
  • mv assig1 assig1.backup
  • mv assig2 assig2.backup
  • mv assig3 assig3.backup

Additional Notes

  1. There is no specific string concatenation operator in Unix.  Simply placing text adjacently achieves the desired result.  Here the string “.backup” is appended to the “assig1”, etc.
  2. As stated earlier, while the use of set braces { } is often omitted when spelling variables, here it helps to clarify the distinction between the variable name and surrounding text.

 

E.g. 2: Count the number of entries in a directory

Print the number of entries in the /etc directory.

count=0
for files in $(ls /etc)
do
   count=$(( $count +1 ))
done
echo The number of entries is $count 

Explanatory notes

  1. The word list consists of a command substitution call of the ls command on the /etc directory.
  2. Expansion of this word list is the names of the entries in the /etc directory.
  3. Each name is substituted into the loop variable files.
  4. The body of the loop consists of incrementing a counter which will eventually hold the number of entries.
  5. Outside the loop, the total number of entries is printed in an appropriate message.

 

 

Eg 3: How to process multiple command line arguments (Method 2)

Let’s say that you want to write your own Unix script that will accept any number of command line arguments:

mycmd parm1 parm2 …

Here is another method, this one using the for command

for cmdarg in $*
do
        myprocess $cmdarg
done
 

Explanatory notes

  1. The special variable $* expands to all command line arguments present starting with $1.
  2. Each of these words is substituted into the loop variable cmdarg.
  3. The body of the loop is run against each command line argument (myprocess is a fictitious Unix command or script).
  4. Unlike the while command example, there is no need for the shift command.
  5. The value of $# is intact after the completion of the for loop.

 

 

 

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