## 5.5. while and until

The remaining two flow control constructs the Korn shell provides are `while` and `until`. These are similar; both allow a section of code to be run repetitively while (or until) a certain condition holds true. They also resemble analogous constructs in Pascal (`while`/`do` and `repeat`/`until`) and C (`while` and `do`/`while`).

`while` and `until` are actually most useful when combined with features we will see in the next chapter, such as arithmetic, input/output of variables, and command-line processing. Yet we can show a useful example even with the machinery we have covered so far.

The syntax for `while` is:

>     while condition
>     do
>         statements ...
>     done

For `until`, just substitute `until` for `while` in the above example. As with `if`, the condition is really a list of statements that are run; the exit status of the last one is used as the value of the condition. You can use a conditional with `[[` and `]]` here, just as you can with `if`.

> NOTE: The only difference between `while` and `until` is the way the condition is handled. In `while`, the loop executes as long as the condition is true; in `until`, it runs as long as the condition is false. So far, so familiar. However, the `until` condition is checked at the top of the loop, not at the bottom as it is in analogous constructs in C and Pascal.

The result is that you can convert any `until` into a `while` simply by negating the condition. The only place where `until` might be better is something like this:

>     until command; do
>         statements ...
>     done

The meaning of this is essentially, "Do statements until command runs correctly." This is occasionally useful, such as when waiting for the occurrence of a particular event. However, we use `while` throughout the rest of this book.

Task 5-5 is a good candidate for `while`.

> #### Task 5-5 Implement a simplified version of the shell's built-in whence command.

By "simplified," we mean that we will implement only the part that checks all the directories in your `PATH` for the command you give as argument (we won't implement checking for aliases, built-in commands, etc.).

We can do this by picking off the directories in `PATH` one by one, using one of the shell's pattern-matching operators, and seeing if there is a file with the given name in the directory that you have permission to execute. Here is the code:

>     path=$PATH:
>     dir=${path%%:*}
>     while [[ -n $path ]]; do
>         if [[ $dir == "" ]] ; then
>             dir="."
>         fi
>         if [[ -x $dir/$1 && ! -d $dir/$1 ]]; then
>             print "$dir/$1"
>             exit 0
>         fi
>         path=${path#*:}
>         dir=${path%%:*}
>     done
>     exit 1

The first line of this code saves `$PATH` in `path`, our own temporary copy. We append a colon to the end so that every directory in `$path` ends in a colon (in `$PATH`, colons are used only between directories); subsequent code depends on this being the case.

The next line picks the first directory off `$path` by using the operator that deletes the longest match of the pattern given. In this case, we delete the longest match of the pattern `:*`, i.e., a colon followed by anything. This gives us the first directory in `$path`, which we store in the variable `dir`.

The condition in the `while` loop checks if `$path` is non-null. If it is not null, it first checks that `$dir` is not null. This could happen for two adjacent colons, which represent the current directory. In this case, `dir` is explicitly set to dot. Next, the script constructs the full pathname `$dir/$1` and sees if there is a file by that name for which you have execute permission (and that is not a directory). If so, it prints the full pathname and exits the routine with a 0 ("OK") exit status.

If a file is not found, this code is run:

>     path=${path#*:}
>     dir=${path%%:*}

The first of these uses another shell string operator: this one deletes the shortest match of the pattern given from the front of the string. By now, this type of operator should be familiar. This line deletes the front directory from `$path` and assigns the result back to `path`. The second line is the same as before the `while`: it finds the (new) front directory in `$path` and assigns it to `dir`. This sets up the loop for another iteration.

Thus, the code loops through all of the directories in `PATH`. It exits when it finds a matching executable file or when it has "eaten up" the entire `PATH`. If no matching executable file is found, it prints nothing and exits with an error status.

We can enhance this script a bit by taking advantage of the Unix utility file(1). file examines files given as arguments and determines what type they are, based on the file's magic number and various heuristics (educated guesses). A magic number is a field in the header of an executable file that the linker sets to identify what type of executable it is.

If filename is an executable program (compiled from C or some other language), typing `file` `filename` produces output similar to this:

>     filename: ELF 32-bit LSB executable 80386 Version 1

However, if filename is not an executable program, it will examine the first few lines and try to guess what kind of information the file contains. If the file contains text (as opposed to binary data), file will look for indications that it is English, shell commands, C, Fortran, troff(1) input, and various other things. file is wrong sometimes, but it is usually correct.

Assume that fred is an executable file in the directory /usr/bin, and that bob is a shell script in /usr/local/bin. Typing `file /usr/bin/fred` produces this output:

>     /usr/bin/fred: ELF 32-bit LSB executable 80386 Version 1

Typing `file /usr/local/bin/bob` has this result:

>     /usr/local/bin/bob: commands text

We can just substitute file for print to print a more informative message in our script:

>     path=$PATH:
>     while [[ -n $path ]]; do
>         dir=${path%%:*}
>         if [[ $dir == "" ]] ; then
>             dir="."
>         fi
>         if [[ -x $dir/$1 && ! -d $dir/$1 ]]; then
>               file $dir/$1
>               exit 0
>         fi
>         path=${path#*:}
>     done
>     exit 1

Notice that by moving the statement `dir=${path%%:*}` to the top of the loop body, it needs to be done only once.

Finally, just to show how little difference there is between `while`, and `until`, we note that the line:

>     until [[ ! -n $path ]]; do

can be used in place of:

>     while [[ -n $path ]]; do

with identical results.

We'll see additional examples of `while` in the next chapter.

### 5.5.1. break and continue

Earlier in this chapter, we saw the break statement used with the `select` construct for breaking out of a loop. break can be used with any looping construct: `for`, `select`, `while` and `until`.

The continue statement is related; its job is to skip any remaining statements in the body of the loop and start the next iteration.

Both the break and continue statements take an optional numeric argument (which can be a numeric expression). This indicates how many enclosing loops should be broken out of or continued. For example:

>     while condition1; do             Outer loop
>         ...
>         while condition2; do         Inner loop
>             ...
>             break 2                  Breaks out of outer loop
>         done
>     done
>     ...                              Execution continues here after break

Programmers will note that the break and continue statements, particularly with the ability to break or continue multiple loop levels, compensate in a very clean fashion for the lack of a `goto` keyword in the shell language.

Copyright © 2003 O'Reilly & Associates. All rights reserved.