Section A.7.  Answers for Chapter 8

Table of Contents

A.7. Answers for Chapter 8

A.7.1. Exercise 1

In this exercise, we have to use three different output methods: to a file, which you're already familiar with; to a scalar (and you'll need Perl 5.8 for this); or to both at the same time. The trick is to store the output channels in the same variable that you'll use for the print statement. When the filehandle is a variable, we can put anything we like in it and decide what to put in it at runtime.

use strict;

use IO::Tee;

my $fh;
my $scalar;

print "Enter type of output [Scalar/File/Tee]> ";
my $type = <STDIN>;

if( $type =~ /^s/i ) {
        open $fh, ">", \$scalar;
elsif( $type =~ /^f/i ) {
        open $fh, ">", "$0.out";
elsif( $type =~ /^t/i ) {
        open my $file_fh,   ">", "$0.out";
        open my $scalar_fh, ">", \$scalar;
        $fh = IO::Tee->new( $file_fh, $scalar_fh );

my $date        = localtime;
my $day_of_week = (localtime)[6];
print $fh <<"HERE";
This is run $$
The date is $date
The day of the week is $day_of_week

print STDOUT <<"HERE" if $type =~ m/^[st]/i;
Scalar contains:

In this program, we prompt the user for the type of output, and we want her to type either "scalar", "file", or "tee". Once we read the input, we detect which one she typed by matching on the first character (using a case-insensitive match for more flexibility).

If the user chose "scalar", we open $fh to a scalar reference. If she chose "file", we open $fh to a file, as you know from before. We name the file after the program name, stored in $0, and append .out to it. If the user chose "tee", we create filehandles for a file and a scalar, then combine both of those in an IO::Tee object that we store in $fh. No matter which method the user chose, the output channels, whether sole or multiple, end up in the same variable.

From there it's just a matter of programming, and it doesn't matter much what we actually print. For this exercise, we get the date string by using localtime in scalar context, then get the day of the week with a literal list slice.

In the string we print to $fh, we include the process ID (contained in the special variable $$), so we can tell the difference between separate runs of our program, and then the date and the day of the week.

Finally, if we choose to send the output to a scalar (either alone or with a file), we print the scalar value to STDOUT to ensure the right thing ended up there.

A.7.2. Exercise 2

use IO::File;
my %output_handles;
while (<>) {
  unless (/^(\S+):/) {
    warn "ignoring the line with missing name: $_";
  my $name = lc $1;
  my $handle = $output_handles{$name} ||=
    IO::File->open(">$name.info") || die "Cannot create $name.info: $!";
  print $handle $_;

At the beginning of the while loop, use a pattern to extract the person's name from the data line, issuing a warning if that's not found.

Once you have the name, force it to lowercase so that an entry for "Maryann" will get filed in the same place as one for "MaryAnn." This is also handy for naming the files, as the next statement shows.

The first time through the loop, the filehandle must be created. Let's see how to do that. The || operator has a higher precedence than the assignment, so it is evaluated first; the program will die if the file can't be created. The ||= operator assigns the filehandle to the hash, and the = operator passes it to $handle as well.

The next time you have the same name in $name, the ||= operator kicks in. Remember that $gilligan ||= $anything is effectively like $gilligan = $gilligan || $anything. If the variable on the left is a false value (such as undef), it's replaced by the value on the right, but if it's true (such as a filehandle), the value on the right won't even be evaluated. Thus, since the hash already has a value for that person's name, the hash's value is used and assigned directly to $handle without having to (re)create the file.

It wasn't necessary to code the castaways' names into this program, because they will be read in as data. This is good because any additional castaway won't require having to rewrite the program. If someone's name is accidentally misspelled, however, it puts some of their data into a new file under the wrong name.

A.7.3. Exercise 3

Here's one way to do it. First, we go through the arguments in @ARGV to find out which ones don't represent directories, then print error messages for each of those.

After that, we go through @ARGV again to find the elements that are valid directories. We take the list that comes out of that grep and send it into map, where we turn each string into an IO::Dir object (ignoring error handling for the moment). The file output list ends up in @dir_hs, which we go through with the foreach loop and send to print_contents.

There is nothing fancy about print_contents, though. It simply takes its first argument and stores it in $dh, which it then uses to walk through the directory.

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

use IO::Dir;

my @not_dirs = grep { ! -d } @ARGV;
foreach my $not_dir ( @not_dirs ) {
        print "$not_dir is not a directory!\n";

my @dirs = grep { -d } @ARGV;

my @dir_hs = map { IO::Dir->new( $_ ) } grep { -d } @ARGV;
foreach my $dh ( @dir_hs ) { print_contents( $dh ) };

sub print_contents {
        my $dh = shift;

        while( my $file = $dh->read ) {
                next if( $file eq '.' or $file eq '..');
                print "$file\n";

Table of Contents
© 2000- NIV