LINUX GAZETTE

[ Prev ][ Table of Contents ][ Front Page ][ Talkback ][ FAQ ][ Next ]

"Linux Gazette...making Linux just a little more fun!"


Learning Perl, part 3

By



The trouble with teaching Perl as a first computer language is that your students won't appreciate it till they start learning their second. The trouble with teaching Perl as a second language is that there's no single suitable first language to go in front.
 -- Larry Wall

When they say that Perl is a `glue language', what they really mean is that it is good for cleaning up after the mistakes of other programs.
 -- Mark-Jason Dominus in comp.lang.perl.misc
 
 

Overview

This month, we'll look at Perl's conditional and looping constructs, and look at a few scripts that use them. We will also explore how they work with Perl's variables, and take a quick look at capturing user input. Once you understand this part, I suggest hacking out a couple of experimental scripts and playing with them; sure, you'll make mistakes - but from this point on, you'll actually need to supplement your reading by getting down and dirty. If you don't play, you can't win...
 
 

Conditionals

Here are the conditional statements that Perl uses; nothing particularly unusual, if you're used to conditionals in other languages. Perl checks if the condition is true or false, and branches the execution based on that.



if    ( traffic_light_is_red ) {     # If condition 1 is true, do
           stop;                     # Action 1
}
elsif ( traffic_light_is_yellow ) {  # If condition 2 is true, do
      hit_the_gas;                   # Action 2
}
else  {
                                     # In all other cases, do
      proceed_with_caution;          # Action 3
}

Note that the "elsif" clause isn't required; neither is the "else". Also, note that "else" is the 'catch-all option': if the light is anything except red or yellow - burned out, just got knocked down by accident, etc. - the action is 'proceed_with_caution'.

Unlike C, even single actions must be enclosed in a block (defined by the curly brackets):

if ( $tomato eq "red" )   print "Ripe.\n";      # WRONG!
if ( $tomato eq "red" ) { print "Ripe.\n"; }    # Right


unless ( $blarg == $foo ) {          # If condition 1 is false, do
       print "Unequal!.\n";          # Action 1
}
else   {                             # Otherwise, do
       print "They're equal.\n";     # Action 2
}

Pretty obvious. It may help to think of "unless" as the "if not" conditional. Once again, the "else" is optional. No, there's no such thing as "elseunless". :)
 
 

Loops

Ah, wonderful loops. These are the things that make actions happen, as many times as we want, based on a condition. You might even say that this loops are the main reasons for computers in general, their main use as the tool that they are: precise repetitive work. Here are the three most
common types of loops under Perl:



while ( $cat eq "away" ) {             # While cond. 1 is true, do
      print "The mice will play.\n";   # Action 1
}



until ( $time > 1159 ) {        # While cond. 1 is false, do
     print "It's morning.\n"    # Action 1
}



The "for" loop can be implemented in two different ways - one is like the "for" loop in C:

for ( $n = 99; $n > 0; $n-- ) {
    print "$n bottles of beer on the wall, $n bottles of beer,";
    ...
}

In this case, we set $n to an initial value (99), decrement it by 1 each time we go through the loop, and check to make sure that it's greater than 0. If it's not, we exit the loop.

The second method, somewhat like the Clipper, FoxPro, etc. "foreach" loops, is by far the most common:

foreach $n ( 0..1000 ) {
        print "Day $n on this deserted island. So far, I've had ";
        print $n * 100, " bananas. I hope I'm rescued soon.\n";
        ...
}

It can also be used this way:

for ( 0..1000 ) {
    print "Day $_ on this deserted island. So far, I've had ";
    print $_ * 100, " bananas. I hope I'm rescued soon.\n";
    ...
}

Our old friend, the "$_" (explained in the previous part of this series.) He does indeed come in handy. Note that "foreach" is just an alias for "for", and they can be used interchangeably.


All of the above conditionals and loops can also be used as single-statement modifiers, as well:

print "This is line $_ of 50.\n" for ( 1..50 );

The above will print 50 lines, numbered in an obvious way.

print "I've found him!" if /Waldo/;

The above line will be printed if the default buffer ($_) contains a match for "Waldo".
 

An interesting fact that combines well with loops and conditionals is that empty variables in Perl return a null value - which is "false". This is perfect for checking them out:

print if $_;            # Prints $_ if it contains anything

The next example shows that a zero value is also false:

print "5280 is true.\n" if 5280;   # This will print.
print "0 is true.\n" if 0;         # This won't print.

Here's an example with a list:

while ( @a ) {
      print pop @a;     # "Pop" the last value off @a and print it
      $count =  @a;     # Get the number of elements in @a
      print $count, " elements left in \@a.\n";
}

When the last element has been popped off, the loop will end.

unless ( %hash ) {
       %hash = ( 'first' =>  'Mighty Joe',
                 'last'  =>  'Young',
                 'type'  =>  'gorilla',
                 'from'  =>  'Pangani Mountains',
                 'born'  =>  '1949',
                 'Mom'   =>  'Jill',
                 'Dad'   =>  'Gregg'
       );
}

If "%hash" is empty, we populate it with some initial values.
 

The range operator, which we've used a couple of times so far, is a useful widget: it allows you to specify a range of numbers or letters. Note that the ranges have to be of the same 'kind' - if you specify ('a'..'Z') or ('A'..'z'), the output will not be what you expect. Also, you cannot specify ('z'..'a'); that won't work either. However, there is an easy way to do that:

foreach $letter ( reverse 'a'..'z' ) {
    print "$letter\n";
}

It will also properly increment "letter lists":

for ( 'aa'..'zz' ) {
    print "$_ ";        # Will print "aa ab ac ... zx zy zz"
}
 
 

User Input

Capturing keyboard input, or input from STDIN in general - such as the lines piped to the input of our script via something like

cat file | perl_script

 - is easy; it's what Perl's "diamond operator" is for.
 

while ( <> ) {        # Capture all keyboard or piped input
      print;          # Print each line as long as input exists
}

The above works exactly like "cat" - it will print all input piped to it, will "cat" a file if it's run with the filename used as an argument, and will accept (and echo) user input until you hit Ctrl-D or Ctrl-C. It can also be written this way:

print while <>;

for a more "Perlish" syntax. Note that "<>" and "<STDIN>" are related but not equivalent:

print while <STDIN>;

will respond to keyboard and piped input, but will not print the contents of a file supplied as an argument. I've never found a situation where I needed that kind of functionality, so I simply use "<>".

If you want to assign user input to a variable, Perl also makes that easy - but there's a bit of a trap built in of which you need to be aware:

$answer = <>;        # Get the input, assign it to the variable
if    ( $answer eq "y" ) {
      print "Yes\n";
}
elsif ( $answer eq "n" ) {
      print "No\n";
}
else {
      print "No idea!\n";
}

The above script will always print "No idea!" Hmm... it looks right; what could be the problem?

The problem is that Perl captures everything that you give it. So, when you type "y", what's the next key you hit? "Enter", that's what! So, the variable stored in $answer is NOT "y", it's "y\n" - the answer and the linefeed. How do we deal with that? Perl, of course, has a function - one you should always use when getting user input:

chomp ( $answer = <> );

"chomp" will remove the linefeed, or "end-of-line" character, from the string to which it is applied. It will also remove EOLs from every element of an array which it receives as an argument. The old Perl4 version, "chop", removed the last character from a scalar (or from the elements of the array) no matter what it was; it's still available if you should need it for that purpose, but for taking user input, use "chomp" (also known, via Perl's error messages, as the "safe chop").
 
 

Exercises For The Mind

Try building a couple of scripts, just for your own education and entertainment:

A script that takes a number as input, and prints "Hello!" that many times. As a bonus, check the input for illegal (non-numeric) characters (hint: use //, the match operator.)

A script that takes the current hour (0-23) as input and says "Good morning", "Dobriy den'", "Guten Abend", or "Buenas noches" as a result. <grin>

If you come up with something particularly clever, don't hesitate to send it to me for the next part of this series: you'll get the credit for writing it, I'll happily dissect it for you, and we'll both become micro-famous and retire to Belize on the proceeds. <laugh>

Don't forget: your shebang line should always contain "-w". If you don't ask Perl to help you with your mistakes, you'll be wasting a lot of time. Let the computer do the hard work!
 

#!/usr/bin/perl -w
print "See you next month!"
 

Ben Okopnik
perl -we'print reverse split//,"rekcah lreP rehtona tsuJ"'


References:

Relevant Perl man pages (available on any pro-Perl-y configured
system):

perl      - overview              perlfaq   - Perl FAQ
perltoc   - doc TOC               perldata  - data structures
perlsyn   - syntax                perlop    - operators/precedence
perlrun   - execution             perlfunc  - builtin functions
perltrap  - traps for the unwary  perlstyle - style guide

"perldoc", "perldoc -q" and "perldoc -f"


Copyright © 2001, Ben Okopnik.
Copying license http://www.linuxgazette.com/copying.html
Published in Issue 65 of Linux Gazette, April 2001

[ Prev ][ Table of Contents ][ Front Page ][ Talkback ][ FAQ ][ Next ]