Lab 4 - Making decisions
(and repeating yourself)

Overview

Last week we took a quick look at two more Perl data types - the list, an ordered collection of items, and the hash, which organizes items by a scalar called the key. Today we're going to get more practice with scalars, lists, and hashes, while learning about some of Perl's control flow constructs. These are ways you can cause your program to make decisions based on variables, or repeat a section of code several times. We'll also get our start with reading information from files on disk, which is something you'll find very handy for the project.

You should know about the following topics by the end of lab today:
  • How to use an if, unless, or if/elsif/else construct.
  • How to loop using a while or for loop.
  • How to use globs to get lists of files.
  • How to read lines of text - or whole arrays - from files on disk.


You'll also write a genuinely useful program -- a simplified version of the grep utility for finding strings in files.

 

Quick review of decision constructs

Perl's if-statement works much like in any other language. Since a scalar could be either a number or a string, we need to use separate comparison operators to tell Perl which comparison we mean.

#!/usr/bin/perl -w
use strict;

my ($a, $b) = (12, 24); # $a = 12 and $b = 24

if( $a > $b ) {
   print "$a > $b\n";
} else {
   print "$a <= $b\n";
}

$a *= 2;

print "$a == $b\n" if $a == $b;
jpr@sulu (i211/lab4) $ ./ifdemo.pl
12 <= 24
24 == 24
jpr@sulu (i211/lab4) $

Notice the shortened form of the if that can be used if there's only one line of code to do when the if is true. Note also that this form is not allowed to have an else.

 

Quick review of looping constructs

We already saw one looping construct in the last lab - the foreach loop. This loop lets us repeat an action for every element in an array. However, sometimes we don't just want to loop over the elements of an array - we may want to do something exactly 10 times (for example), or keep reading lines of input from the user until they enter "done". For these problems we need different types of loops - the while loop and the for loop.

Note that neither the while loop or the for loop is more powerful than the other - everything you can do with a while you can do with a cleverly-written for, and vice-versa. However, choosing the appropriate loop can make your code easier to read.

#!/usr/bin/perl -w
use strict;

# code that processes user input might look like this..

my $input = "";
# read a line of input..
while( $input ne "done" ) {
   # do something with the line of input
   # read another line of input
}

# print the numbers from 1 to 10
for( my $i = 1; $i <= 10; $i++ ) {
   print $i, "\n";
}

 

More practice with hashes

Let's write a simple program that maintains bank accounts. The core of this program is a "database", implemented as a hash, that associates every user with the balance in their account.

The program should repeatedly prompt the user for commands. This command can be "add", "wd" (short for withdraw), "print", or "exit". An example run of this program is shown below - try to figure out exactly what the program is supposed to do before you start writing code!

jpr@sulu (i211/lab4) $ ./bank.pl
Welcome to Techno-Bank 3000.
Enter command => add jacob 1000
done.
Enter command => add jacob 10000
done.
Enter command => this is fun
invalid command.
Enter command => add fil 1000
done.
Enter command => wd fil 20
done.
Enter command => print
Here are the accounts in Techno-Bank 3000.
jacob has 11000.
fil has 980.
Enter command => exit
Goodbye.
jpr@sulu (i211/lab4) $

The first step in solving this assignment is to think about it one step at a time. The following skeleton program can give you an idea of what needs to be done, and serve as a starting point.

#!/usr/bin/perl -w
use strict;

my %accounts; # create the hash that stores everything

my $next_line = < STDIN>;
chomp $next_line;

# this trick can help you separate the command from the username and amount
my ($command, $user, $amount) = split / /, $next_line;

while( the next command is not "exit" ) {
    # figure out what the next command is
    # do the appropriate thing
    # if the command is invalid, print an error message.
    # Print another prompt
    # read another command
}

Do not worry about overdrafts (withdrawing more money than a person has) or invalid amounts (e.g. entering a word instead of a number for the amount). Implement the "exit" command first, then "print", then "add", then "wd" - doing things in this order will make testing easy. Try to run your program often to make sure errors don't pile up.

Do this:
Write bank.pl. If you choose, you can use the code above to help you get started. Make sure to place the finished program in your i211/lab4 directory. Test your program thoroughly to make sure it works right.

Hint:The body of your while loop will probably be a big if/elsif/else block.

 

Quick review of globs and file I/O

The "angle brackets" (<, >) are used for two things in Perl - globs, and reading from files.

Globs let you grab the names of all files in the current directory that match a certain pattern, and return these names as a list.

#!/usr/bin/perl -w
use strict;

my @perl_scripts = <*.pl> # * means "anything" here

foreach my $perl_script (@perl_scripts) {
   # do something...
}

The angle brackets can also be used to read from files. This program copies the file "a" to the file "b".

#!/usr/bin/perl -w
use strict;

open my $infile, "a" or die;
open my $outfile, ">b" or die;

while( my $nextline = <$infile> ) {
   print $outfile $nextline;
}

close $infile;
close $outfile;

The angle brackets can also be used to read an entire file at once, as an array of lines. To do this, simply evaluate the angle brackets in list context. The following program copies a to b using arrays.

#!/usr/bin/perl -w
use strict;

open my $infile, "a" or die;
open my $outfile, ">b" or die;

my @lines = <$infile>;
print $outfile @lines;

close $infile;
close $outfile;

 

Practice with globs and file I/O

Here, let's write a useful program - a simplied version of the grep program, which searches for things in files. Your program should be able to be run with two command line arguments - a file extension (like .pl) and a string to find. It should then print out all the lines in files with that extension that contain the string, in the following format:

jpr@sulu (i211/lab4) $ ./mygrep.pl .txt hello one.txt 5 : hello, this is a line in one.txt two.txt 12 : At least you could have said hello, she said. jpr@sulu (i211/lab4) $

That is, your program should print the file name and line number where each match occurs. What follows is a skeleton that may help you get started.

#!/usr/bin/perl -w
use strict;

# get command line arguments.
my ($extension, $str) = @ARGV;
die unless $extension and $str; # make sure both are defined.

my @files = # appropriate glob goes here

foreach my $filename (@files) {
   # open the file, read all of its lines into an array, then close it.
   # use a for-loop to loop over the array (so you can keep track of line number)
   # match each line against $str to see if it matches.
   #      if( $line =~ /$str/ ) { ... is one way to do that.
   # if it matches, print out the filename, line number, and the line that matches.
}

Do this: Write mygrep.pl. Pay close attention to the requirements, and just ask if you are unsure about anything. Make sure you put your file in the i211/lab4 directory.

 

Turn in your assignment
As usual, let's make a tarball of your assignment files (make sure they are ALL in the lab4 directory!) For this lab, you should have the following files:
  • bank.pl
  • mygrep.pl
As a reminder, here's the command you can use:

jpr@sulu (i211/lab4) $ cd ..
jpr@sulu (i211) $ tar cvz lab4 > lab4.tar.gz
bank.pl
mygrep.pl
jpr@sulu (i211) $

You should then download this lab4.tar.gz file and upload to the Lab 4 assignment on Oncourse. Make sure you press "submit" and "finish" enough times to actually upload your file. We will take off points for incorrect submissions, even if you really finished the lab on time.

Okay, that's it! Have a good weekend.

Thanks to Jacob Ratkiewicz for lab content, Sid Stamm for CSS stylesheets, and Rob Signorelli for original syntax-highlighting JavaScript.