shell programming - loops

created onJanuary 18, 2022

In Bash shell programming you can use the usual suspects for loop structures: the for loop, which is usually used to iterate over a set of items, and the while and until loop structures, which both loop while a certain specified condition is met.

for loop

The for loop usually iterates over a set of items, and executes a block of statements and expressions for each item. The general form is:

for <item set>; do # expressions / statements done

The can be one of several types of item sets:

  • an array, i.e an array of fruits defined as
  • a range, i.e. for the range of integers from 0 to 7
  • C-style loops, i.e. for for the range of integers from 0 to 7 (the expression is equivalent to the range expression above)

An infinite loop can be written by using an infinite item set:

iterating over an array

An example of a for loop iterating over an array:

#!/bin/bash fruits=(apple kiwi banana) for i in ${fruits[@]}; do printf ' %s\n' ${i} done

The same loop can be written with the array defined in the for statement:

#!/bin/bash for i in "apple kiwi banana"; do printf ' %s\n' ${i} done

iterating over a range of numbers

You can iterate over a range with . The step rate can be omitted. The following for loop prints the numbers from 0 to 7:

#!/bin/bash for i in {0..7}; do printf ' %s\n' ${i} done

While this for loop prints all even numbers from 0 to 7:

#!/bin/bash for i in {0..7..2}; do printf ' %s\n' ${i} done

You can also reverse the range. The following for loop prints all even numbers from 7 to 0:

#!/bin/bash for i in {0..7..2}; do printf ' %s\n' ${i} done

iterating of a range of characters

Ranges also work with characters. The following loop prints all lower case characters from a to e:

#!/bin/bash for i in {a..e}; do printf ' %s\n' ${i} done

Ranges of characters can also be reversed and the step rate also works with characters. The following for loop prints the upper case characters E, C and A: letters

#!/bin/bash for i in {E..A..2}; do printf ' %s\n' ${i} done

C-style for loops

Well, not really C-style but rather roughly based on C. Here’s an example of a for loop that prints the numbers 0 to 7:

#!/bin/bash for ((i=0; i<=7; i++)); do printf ' %s\n' ${i} done

An infinite C-style for loop can be written by omitting start value, stop, value and step rate:

#!/bin/bash for ((;;)); do printf 'whatever\n' done

The step rate can be omitted and the variable defined in the for statement can be manipulated in the for body instead:

#!/bin/bash for ((i=0; i<=7;)); do printf '%s\n' ${i} (( i+=1 )) done
The example above makes no sense at all and only serves for demonstration purpose. If one would use a for statement without a step rate, the variable defined in the for statement would be manipulated in the for body depending on some condition that is checked in the for body. In most of such cases the while or the until loop are a better choice.

while loop

A while loop executes a block of statements and expressions as long as the condition specified in the while statement is true.

The general form of a while loop is:

while <condition>; do # statements / expressions done

can be anything that returns an exit status. An exit status of 0 means true, while an exit status of 1 means false.

I.e., printing the numbers from 0 to 7 with a while loop:

#!/bin/bash i=0 while [[ ${i} -le 7 ]]; do printf '%s\n' ${i} ((i+=1)) done

Or, with arithmetic expansion in the condition, which I find more legible, again the numbers 0 to 7 are printed:

#!/bin/bash i=0 while ((i <= 7)); do printf '%s\n' ${i} (( i+=1 )) done

until loop

An until loop executes a block of statements and expressions as long as the condition specified in the until statement is false.

The until loop below prints the numbers 0 to 7:

#!/bin/bash i=0 until (( i>7 )); do printf '%s\n' ${i} ((i+=1)) done

As with the while loop, can be anything that returns an exit status. An exit status of 0 means true, while an exit status of 1 means false.

infinite while and until loops

Infinite while and until loops can be written with the command for while loops and for until loops, i.e.:

#!/bin/bash while true; do printf '%s\n' "infinite loop!" done

You might come across an alternative form of the while or until loop, which uses the bash builtin operator (colon), which evaluates its arguments and always returns true:

#!/bin/bash while :; do printf '%s\n' "infinite loop!" done

Of course, for an infinite until loop using the colon operator, the return code of must be inverted:

#!/bin/bash until ! :; do printf '%s\n' "infinite loop!" done

This application of the colon operator has been documented in the The UNIX Programming Environment manual:

" ":" is a shell built-in command that does nothing but evaluate its arguments and return "true". [...], we could have used true, which merely returns a true exit status. (There is also a false command.) But ':' is more efficient than true because it does not execute a command from the file system. "
The UNIX Programming Environment, Kernighan and Pike, 1984

Given the speed of harddisks back in 1984 this made sense in 1984. Nowadays, the speed advantage is negligible, thus it’s better to use the commands , or .

continue and break in loops

continue skips the statements and expressions of the block after the continue statement. Or, in other words. it’s a goto to the end of the block.

break immediately terminates the loop, that is, with break you immediately bail out of the loop.

In the following example, in the for statement the range of the numbers 0 to 7 is defined. When the block statments and expressions are executed, a value of 2 for the variable i causes the block to be continued at the beginning of the block, with the variable i incremented to the value 3. This happens before the printf statement, so the variable value 2 is not printed. The break statement together with the check of the variable i for a value of 4 causes an abort of the for loop after the value 4 has been printed. Thus, the following script prints out the numbers 0, 1, 3 and 4 and terminates thereafter:

#!/bin/bash for i in {0..7}; do if [[ ${i} -eq 2 ]]; then continue fi printf ' %s\n' ${i} if [[ ${i} -eq 4 ]]; then break fi done