dcsimg

Loops at Last

On a cross-country plane trip, you sometimes reach an altitude from which you can see where you've been. This month's column on the topic of loops is a high-altitude point in our exploration of the bash shell. Much of what has been explained so far is a prerequisite for understanding loops. Computers are powerful because they can quickly and accurately perform repetitive operations, and the loop is the shell construct that puts this power right into your hands.

On a cross-country plane trip, you sometimes reach an altitude from which you can see where you’ve been. This month’s column on the topic of loops is a high-altitude point in our exploration of the bash shell. Much of what has been explained so far is a prerequisite for understanding loops. Computers are powerful because they can quickly and accurately perform repetitive operations, and the loop is the shell construct that puts this power right into your hands.

The while Command

The shell provides three different loop commands:


  • while
  • until
  • for

The while command, the simplest of the three, has this form:


while test
do
body
done

Notice that the command includes three keywords (while, do, and done) and two user-supplied components (test and body). The command is often written as shown, on four lines. However, the command can also be written on a single line by inserting semi-colons that enable the shell to parse it:


while test; do body; done








Newbies Fig1
Figure One: The syntactic structure of the while command.

Computer scientists sometimes refer to keywords and punctuation as syntactic sugar. In this respect, the while command is indeed sugar-laden. Figure One presents the command’s syntax visually, using a simple railroad track diagram.

Here’s an example while command:


while test “$1″ != “”
do
echo $1
shift
done

The command’s operation will be explained in the next section. For now, let’s focus on the command’s syntax. By referring to Figure One, you should be able to determine that the test component of the command consists of the expression:


test “$1″ != “”

The test component of a while command often (though not always) consists of a test command. The body component of our example command consists of the expression:


echo $1
shift

Now that we can pick out the parts of a while command, let’s check out how the command actually works.




Is Your Shellmanship Sufficient?

To understand the relatively simple while command given in the example, you must know quite a bit about the shell. In particular, you must understand:


  • the test command
  • the echo command
  • the shift command
  • shell arguments
  • exit status

Let’s quickly review the essentials. The test command, explained in the September column, compares values you specify and places the result in the shell variable $? , also known as the exit code or exit status. The echo command displays its arguments on the console. You can use the man command to learn more about the test and echo commands. Shell arguments are the arguments that were specified when a shell script was invoked. The shift command (explained in the August column) shifts shell arguments to the left: the first argument is discarded, the second becomes the first, the third becomes the second, and so on.

If this brief review is overwhelming, don’t fret. The previous articles in this series are available on the Linux Magazine Web site at http://www.linux-mag.com/depts/newbies.html/ — you can review them there and fill in any gaps in your shellmanship.


What To Do While Looping

Every shell command has two structures. The syntactic structure governs how the command should be written. The semantic structure determines what the command does.








Newbies Fig2
Figure Two: The semantic structure of the while command.

The preceding section described the syntactic structure of the while command. Figure Two illustrates the command’s semantic structure. Essentially, the while command repeatedly executes the command (or commands) comprising its body until the test command returns a non-zero exit status.

When you’re ready to study the operation of the sample while command from the preceding section, put it in a script file named looper and invoke the script like this:


sh looper a b c

The output consists of the script’s arguments, printed one per line:


a
b
c

When the shell executes the sample while command, it does the following:

1. It executes the test command, comparing the value of the first shell argument to the empty string, setting the exit status to zero if they match.

2. If the exit status is non-zero, the script is complete. Otherwise, it continues to the next step.

3. It executes the echo command, printing the value of the first shell argument.

4. It executes the shift command, discarding the value of the first shell argument and shifting the remaining shell arguments left.

5. Finally, it goes back to step 1.

Now that we’ve looked at the syntax and semantics of the while command, let’s meet its sibling, the until command.

The until Command

The until command closely resembles the while command, both in syntax and semantics. The command has this form:


until test
do
body
done

And like the while command, the until command has a more compact, alternate form:


until test; do body; done

The key difference between until and while is how they determine when it’s time to stop looping. The until command will keep going until the test is true. This is the opposite of the while command, which instead waits for the test to be false.

Between while and until, you may feel like you’ve got all the loops you need. But, there’s one more member of the shell’s loop command family — the for command.

The for Command

The for command is the black sheep of loop commands, resembling neither of its siblings. It has the following form:


forvariablein list
do
body
done

In fact, the for command has a few things in common with the others. For example, it has a body component and an alternate, compact form:


for variable in list; do body;
done

The big difference is that in place of the test component, the for command has variable and list components. It’s specialty is iterating over lists.

It also has a few tricks up its sleeve. It’s permissible to omit the keyword in and the list component, in which case the value of the shell variable $@ is taken as the list value. ($@ is a shell variable that contains a list of command-line arguments which are presented to the shell script.) Here is an example:


for i
do
echo “$i was a command-
line argument”
done

Here is another simple for loop with an explicit list:


for i in a b c
do
echo $i
done

If you execute this for loop, you’ll obtain the output:


a
b
c

Notice that the variable component of this command consists of the name i. This name corresponds to a shell variable that is referenced in the loop body as $i. Notice also that a single line of output is generated for each member of the list component of the for command.

The operation of the command is driven by its list component. The body of the loop is executed once for each item in the list. Each time the body is executed, the value of the shell variable designated by variable is set to the current list item.

Let’s Be Practical

Okay, I admit it. Most of the code examples that I have presented in this series have been rather contrived and useless. This was largely due to the absence of loops. However, now that we know how to write scripts that have loops in them, we can accomplish truly marvelous feats. Let’s wrap this all up with a more useful example of loops in action.

For this example, we’ll look at an application that maintains mirrors of important FTP sites. The example is run by the Linux cron daemon, which runs scripts at specified times. It’s often used to perform clean up tasks overnight, when few users are likely to be logged on.

Under Red Hat Linux, cron executes the scripts residing in the directory /etc/cron.daily on a daily basis. (If you use another distribution, you may find that cron is configured somewhat differently.)

In the /etc/cron.daily directory, we can include the following script, which we’ll call update-mirrors:


#!/bin/sh
cd /root/mirror
for i in *
do
/usr/bin/mirror $i
done

This script uses a program called mirror that’s used to make a local copy of the specified parts of an FTP site. The mirror program requires a file called a package that identifies the FTP server, user name, password, and other information necessary for copying an FTP site. For more information on mirror, see http://sunsite.doc.ic.ac.uk/packages/mirror/.

Suppose that we keep our mirror packages in the directory /root/ mirror. When the /etc/cron. daily/update-mirrors script is executed, it sets the current working directory to /root/mirror and then executes /usr/bin/ mirror on each script residing there. Thanks to the for loop, placing a new package in /root/ mirror automatically schedules it for execution.

Parting Thoughts

Loops can make you dizzy, so if you’re feeling disoriented, take a break and return when everything stops spinning. The bottom line is that shell scripting can save you a whole lot of work, so the time that you spend mastering techniques such as loops will be well worth the effort. Until next month, happy looping!




Auxiliary Commands

The shell defines two auxiliary commands that are useful in combination with loops. The break command immediately terminates execution of the loop in whose body it appears. The continue command terminates execution of the current iteration of the loop in whose body it appears. To be useful, these commands are generally executed conditionally.

For example, here’s a command that ceases execution when it finds a script argument having the value x :


for i
do
if test “$i” = “x” then
break
fi
echo $i
done

If the command is placed in the file breaker , the command:


sh breaker a b x c

prints the result:


a
b

And, here’s a command that displays the values of script arguments other than x :


for i
do
if test “$i” = “x”
then
continue
fi
echo $i
done

If the command is placed in the file continuer , the command:


sh continuer a b x c

prints the result:


a
b
c




Bill McCarty is an Associate Professor of Finance at Azusa Pacific University. He can be reached at bmccarty@apu.edu.

Comments are closed.