04. Selection and Looping (Part 2)

while

  • The while loop repeats a block of command as long as a condition is true
  • Format:

while [ conditional expression ]

do

commands

done

  • You can use [[ ]] or (( )) in place of [ ]
  • The shell runs the loop in this order:
  • Evaluate the conditional expression
    • If it evaluates to false, skip the command block and go to the command that is after done (the loop is finished)
    • If it evaluates to true, then run the commands once
  • Loop back and evaluate the conditional expression again
  • Note that the condition is evaluated in the first step, so it is possible that the loop does not run at all if the condition is false at the beginning
  • It is recommended that you keep the indentation as shown
  • Example

num=10

while [[ $num –ge 0 ]] # while num >= 0

do

echo $num # print num

(( num-- )) # then decrement num

done

echo ‘Blast off!’ # After printing the countdown

# sequence, print Blast off!

until

  • The until loop repeats a block of command until a condition is true
  • Format: until [ conditional expression ]

do

commands

done

  • You can use [[ ]] or (( )) in place of [ ]
  • The shell runs the loop in this order:
  • Evaluate the conditional expression
    • If it evaluates to true, skip the command block and go to the command that is after done (the loop is finished)
    • If it evaluates to false, then run the commands once
  • Loop back and evaluate the conditional expression again
  • Note that the condition is evaluated in the first step, so it is possible that the loop does not run at all if the condition is true at the beginning
  • It is recommended that you keep the indentation as shown
  • Example

until (( num == 0 )) # until num is 0

do

read –p “number to square (0 to stop) : ” num # prompt for num

(( num = num * num)) # square num

echo $num # print square

done


for in

  • The for in loop iterates over an argument list. For every element in the list, it runs the loop one time
  • Format:

for element in list

do

commands

done

  • With every pass of the loop, each element in the list is stored in the variable element. The commands usually access element to do some work with it
  • It is recommended that you keep the indentation as shown
  • Examples

for file in ~ # for each file in the home directory

do

ls –F $file # list each file

done

for num in 1 3 5 7 9

do

echo $num # will print 1,3,5,7,9 on separate lines

done


Nested Loop Constructs

  • All loop statements can be nested. Within each loop there can be another loop
  • Selection and loop statements can also be nested together. Within a selection block of commands there can be a loop. Likewise, within a block of commands for a loop, there can be selection statements
  • For example, what does the following code do?

count=0

for file in *

do

if [ -f $file ]

then

(( count ++ ))

fi

done

echo $count regular files


break

  • The break statement stops any of the 3 loops
  • When execution reaches break, it jumps to the command after the loop and the loop is finished
  • break is typically part of an if construct because you want to stop the loop only when a certain condition is true
  • Using break can create hard to maintain code because the loop control is being ignored when execution breaks out of the loop
  • For example:

for num in 1 2 3 4 5 6 7 8 # looks like the loop runs 8 times

do

echo $num

if [ $num –eq 5 ]

then

break # but, surprise, it actually runs 5 times

fi

done

  • For this reason, you should think twice about using break to stop a loop. If there is a condition that should cause a break in the loop, then that condition should be part of the loop control condition.
  • In the example above, for num in 1 2 3 4 5 would be much better coding practice


continue

  • The continue statement causes execution to skip the current iteration of the loop and go on to the next iteration
  • When execution reaches continue, it jumps to the top of the loop and starts the next iteration of the loop
  • continue is typically part of an if construct because you want to skip an iteration only when a certain condition is true
  • Using continue can create hard to maintain code because the loop control is being ignored when execution skips an iteration
  • For example,

for num in 1 2 3 4 5 6 7 8 # even though the loop goes from 1 to 8

do

if [ $num –eq 5 ]

then

continue # 5 will not get printed

fi

echo $num

done

  • For this reason, continue should be used only when checking for an error condition and the loop should not run for the iteration that has the error

Debugging

  • The shell provides a –x option to help you debug your script
  • When the –x option is turned on, the shell prints the command in the script before it runs that command
    • This can help you see all the iterations of a loop and all the decision making with an if construct
  • To turn the –x option on, put set –x in your script where you want the shell to start printing the command
    • To turn the –x option off, put set +x in your script where you want the shell to stop printing the command
    • By using –x and +x options, you can isolate the part of the script that you want to debug
  • When debug mode is set and the shell prints the command, it adds a + symbol in front of the command, to make it easier for you to differentiate between a command that’s being printed and the script output that’s being printed.
  • If you want to change the + symbol to something else, change the environment variable PS4. Make sure you also export it so the bash child process that’s interpreting your script can inherit the changed PS4 value
  • In addition to the –x option, there are some common debugging techniques that programmers should always use
    • Write one logical block of code at a time, then test and debug it until it works before adding more code. Writing an entire script without incremental debugging is just asking for a headache!
    • Don’t underestimate the power of the echo utility to print out variable values. Printing intermediate values is one of the classic and most often used debugging techniques

If you use vi as your text editor, here are some basic tips to make coding and debugging easier.

  • The command: nG
    • means vi will go to line n. This is useful when the error is on line n and you want to get there fast
  • The repeat command: . (a single period)
    • is to repeat the previous command that changes text. This is useful when you need to make the same change in several places in your code
  • The substitute command works just like the sed s command and will accept regular expressions as well as \t and \n escape characters
  • The command :set number
    • means vi will show line numbers along with your code. This is very useful when the error message refers to a certain line number
  • The command :set ts=2
    • means that when you hit the tab key, it will indent by 2 spaces.
    • Indenting is important in making code readable, and most programmers prefer to use the tab key rather than hitting the space bar several times to get the same effect.
    • The default value for tab in vi is 5 spaces and I find that 5 is too much indentation, especially when there are nested blocks of code. Therefore you can adjust the tab spacing something smaller than 5.
  • The 2 set commands only last for the current vi session, so when you log out, the setting is gone.
    • To make the setting last across sessions, create a hidden file called .exrc in your home directory. In that file, enter the 2 set commands without the preceding : and with one command per line. When you run vi the next time, the setting will take effect.