Copyright Notice

This text is copyright by CMP Media, LLC, and is used with their permission. Further distribution or use is not permitted.

This text has appeared in an edited form in SysAdmin/PerformanceComputing/UnixReview magazine. However, the version you are reading here is as the author originally submitted the article for publication, not after their editors applied their creativity.

Please read all the information in the table of contents before using this article.

Unix Review Column 21 (Aug 1998)

Most modern algorithmic languages have some various ways of doing ``decision'' and ``iteration'', or they'd be rather useless (or useful only for trivial programs). Perl has many different ways of choosing and evaluating loops. Let's look at some of them.

The basic element for choice is the if statement:

    if ($somecondition) {
      true_branch;
      true_branch;
    } else {
      false_branch;
      false_branch;
    }

along with its cousin, the elsif clause:

    if ($cond_1) {
      true_branch_for_1;
      true_branch_for_1;
    } elsif ($cond_2) {
      true_branch_for_2;
      true_branch_for_2;
    } elsif ($cond_3) {
      true_branch_for_3;
      true_branch_for_3;
    } else {
      all_false_branch;
      all_false_branch;
    }

Each of the conditions here are executed in turn, and the first one that results in true causes its corresponding branch to be executed, skipping all the others. A handy construct.

But there's also some other ways of getting at the same thing. Many of these are listed in ``man perlsyn'' (or ``perldoc perlsyn'' if you don't have the manpages properly installed). For example, another if statement might be written as:

    $cond && do {
      true_branch;
      true_branch;
    };

Or, we might even flip it around with a ``backwards-if'', as in:

    do {
      true_branch;
      true_branch;
    } if $cond;

Personally, I don't use that one often. It seems to be too backwards for me. However, its much-simpler form of:

    $action_expression if $cond;

is something I use frequently. For example:

    print "a is $a, b is $b\n"
      if $debug;

Here, I'd be setting $debug at the beginning of the program, and all of these statements spring to life.

To get some more choices for choice, let's first look at the building blocks of iteration. The basic while loop gets us repeated executions of a block until a condition fails:

    while ($cond) {
      body;
      body;
    }

We can break out of this loop early with last, as in:

    while ($cond) {
      body;
      last if $cond_2;
      body;
    }

Here, a backwards-if breaks us out of the loop when $cond_2 is true. Because last counts as an expression, we can use it in comma-expressions to do multiple things:

    while ($cond) {
      body;
      ($a = "something"),
      ($b = "something else"),
      last
        if $cond_2;
      body;
    }

Once again, I prefer keeping the left side of a backwards-if rather simple, so I use something like this sparingly. But consider the somewhat noisier alternative:

    while ($cond) {
      body;
      if ($cond_2) {
        $a = "something";
        $b = "something else";
        last;
      }
      body;
    }

which works, but takes up a few extra lines visually. There's also the next operator, bouncing forward when we see something we don't want to process:

    foreach $item (split) {
      body_1;
      next if $item =~ /^a/;
      body_2;
    }

Here, the items that begin with ``a'' are processed with body_1, but not body_2. The next operator automatically respects a continue block, which can be handy sometimes:

    foreach $item (split) {
      body_1;
      next if $item =~ /^a/;
      body_2;
      next if $item =~ /z$/;
      body_3;
    } continue {
      body_4;
    }

Here, items that start with ``a'' do only body_1 and body_4, while those that end in ``z'' do body_1, body_2, and body_4, and everything else does all 4 body steps. Pretty flexible. A pratical use for this is getting the $. variable correct while processing the <> filereading. You need to close ARGV when you've seen end of file, but only after you've done all the processing on that line. So the simplest assured loop looks like:

    while (<>) {
      next unless /perl/i;
      print "$ARGV:$.:$_";
    } continue {
      close(ARGV) if eof;
    }

By having the close be inside the continue, we are guaranteed to execute it even if we've done a next.

Then there's the redo operator, which restarts the current iteration. Here's a nice little practical example for redo -- handling continuation lines in a configuration file:

    while (<>) {
      s/#.*//;
      next unless /\S/;
      if (s/\s*\\\s*\n//) {
        $_ .= <>;
        redo;
      }
      ...;
    }

Here, we're reading a line at a time into $_. If the line contains comments, they get stripped away. If the line is completely blank (doesn't contain any non-blank characters), it's ignored. The tricky part is the substitution. Any backslash near the end of the line (optionally surrounded by whitespace) will be stripped, and we'll append the next line from the input.

However, that now leaves us with a line that could need comment-stripping, blank-checking, or possibly additional backslash processing, so the easiest way is to redo, which jumps back up to the top of the loop. Nice and compact.

The last, next, and redo operators apply to a ``naked block'' as well as the iterators (while, until, for, and foreach). With a naked block (one that's not part of a larger construct), we can make rather arbitrary control structures. For example, let's add all the numbers that are entered one at a time, letting the word ``end'' stop us.

    {
      print "number? ";
      chomp($n = <STDIN>);
      last if $n eq "end";
      $sum += $n;
      redo;
    }

Here, the naked block serves as the boundaries for the enclosed last and redo operations. This problem can be solved in other ways, but we've essentially created a loop that enters in the middle, or exits in the middle, depending on how you look at it. We can also create the multiway if tests using naked blocks. Let's look at some rudimentary @ARGV processing: { last unless $ARGV[0] =~ /^-/; $_ = shift; last if /^--$/; $verbose++, redo if /^-v$/; $radians++, redo if /^-r$/; $expr = ($1 || shift), redo if /^-e(.*)$/; $file = ($1 || shift), redo if /^-f(.*)$/; die ``unknown arg: $_''; }

Here, we process @ARGV entries until we don't have an initial dash. If the first element starts with a dash, we shift it off into $_. If that's exactly a double dash, we stop looking as well. However, if it's a -v or a -r, we set the corresponding variable, and jump back up to the top. If it's -e or -f, we take the remainder of the arg (or the next arg if no further text) as a parameter. Finally, if we make it through the maze and don't see something we like, we can bail out.

You could also flip this around with the and and or operators, as in: { $ARGV[0] =~ /^-/ or last; $_ = shift; /^--$/ and last; /^-v$/ and $verbose++, redo; /^-r$/ and $radians++, redo; /^-e(.*)$/ and $expr = ($1 || shift), redo; /^-f(.*)$/ and $file = ($1 || shift), redo; die ``unknown arg: $_''; }

This does exactly the same thing as the previous example. While I was starting to write this example, I went ``yuck'', because I'm somewhat opposed to using the and and or operators in a void context (that is, simply for their side effects of conditionally executing their right-hand operand). However, after looking at this code, I'm actually more in favor of it, because it looks cleaner to me. The downside is that someone looking at this code might not understand how and and or work, so a comment above to that effect might be handy.

Well, I hope that gives you a few more choices for choices, and lets you cycle through a few more forms for iterations. Always remember the Perl motto: There's more than one way to do it! See you next time.


Randal L. Schwartz is a renowned expert on the Perl programming language (the lifeblood of the Internet), having contributed to a dozen top-selling books on the subject, and over 200 magazine articles. Schwartz runs a Perl training and consulting company (Stonehenge Consulting Services, Inc of Portland, Oregon), and is a highly sought-after speaker for his masterful stage combination of technical skill, comedic timing, and crowd rapport. And he's a pretty good Karaoke singer, winning contests regularly.

Schwartz can be reached for comment at merlyn@stonehenge.com or +1 503 777-0095, and welcomes questions on Perl and other related topics.