Main index

'While' and 'until' loops

You may wish to execute a sequence of commands for a variable number of times while a certain specified condition holds. The `if` statement allows a single test to be carried out; multiple tests can be carried out using `while`. The syntax is

`while `command1
`do    `command2
`done`

indicating that command1 is executed repeatedly. Each time its exit status is checked, and if the exit status is zero, command2 is executed.

Note that the exit status of `true` is always `0`.

As soon as command1 yields a non-zero exit status, the `while` loop ceases immediately. As a simple example of a while loop, the following will display `tick` on your terminal repeatedly once a second:

```\$ while true > do >   echo tick >   sleep 1 > done```

Don't forget to press ctrl-C to stop it!

Worked example

Use a `while` loop to print out the 'twelve times table':

```1 x 12 = 12
2 x 12 = 24
...
12 x 12 = 144
```
Solution: Use a `while` loop and `bc` to do the calculations. Set a variable `i` to start at `1` and then become in turn `2`, `3`, up to `12`. While the value of `i` is less than or equal to `12` evaluate `\$i * 12` using `bc`, storing the answer in variable `result`, display the line of the table, and add one to `i` using `bc` again.

```\$ i=1 \$ while [ \$i -le 12 ] > do >   result=\$( echo "\$i * 12" | bc ) >   echo "\$i x 12 = \$result" >   i=\$( echo "\$i + 1" | bc ) > done```

Similar to `while` is `until`; the syntax is the same as `while`, but instead of the condition that command1 must succeed for the loop to continue to execute, command1 must fail, and the loop finishes when the condition gives non-zero exit status. So

`until `command1
`do    `command2
`done`

indicates that command1 is executed repeatedly. Each time its exit status is checked, and if the exit status is not zero, command2 is executed. As soon as command1 yields a zero exit status, the `until` loop ceases.

Worked example

Write a script to repeatedly request names of files to be displayed, until you type in `QUIT` to stop.
Solution: Use an `until` loop repeatedly to read in the name of a file, and then (after having checked that it can be read) displayed it. Note that we commence by setting the value of the filename, stored in the variable `FILENAME`, to `""` (i.e. the null string). This is advisable, just in case the user running the script has already set `FILENAME` to `QUIT` - in which case the script would stop immediately it had begun to run. This may appear highly unlikely, but you should always err on the side of caution.

```FILENAME=""                     # Initialise FILENAME
until [ "\$FILENAME" = "QUIT" ]  # Finish when value is QUIT
do
echo "Name of file to print (or QUIT to finish):"
if   [ -r "\$FILENAME" ]      # If it's readable ...
then lp "\$FILENAME"          # print it
fi
done
```

Two other commands are provided for use in `while`, `until` and `for` loops. The first one is `break`, which is a method of breaking out of a loop. If a `break` command is encountered the immediately enclosing loop will terminate immediately. The other command is `continue`; unlike `break`, instead of completely leaving the loop, control passes back to the beginning of the loop.

Worked example

A file called `core` is sometimes created when a program 'crashes' - it is very big, and you will often need to delete it. Write a script to check once a minute to see whether you have created a file called `core` in your home directory, and to terminate with a message on your terminal warning you of this fact.
Solution: There are several ways of approaching this, and we present two possible solutions. Both use loops, check the existence of the file `core` using the `test` command, and `sleep` for 60 seconds between tests. The first uses `until`:

```until [ -f \$HOME/core ]   # Stop when \$HOME/core exists
do
sleep 60               # Wait one minute
done
echo core file created    # Notify the user
```

The second solution involves looping forever. Within each loop it does the `test` and, if this detects the file, it uses `break` to leave the loop:

```while true                # Forever ...
do
sleep 60               # Wait one minute ...
if   [ -f \$HOME/core ] # If \$HOME/core exists ...
then break             # leave the loop
fi
done
echo core file created    # Notify the user
```

Try running one of these scripts in the background. You can create a file `core` yourself, using `touch`, say, to check that it does indeed work:

`\$ touch core`

Instead of writing a shell script, you might have considered `crontab` for this task.