Документация
HTML CSS PHP PERL другое

Section 8.4.  IO::Handle

 
Previous
Table of Contents
Next

8.4. IO::Handle

Behind the scenes, Perl is really using the IO::Handle module to work this magic, so our filehandle scalar is really an object.[*] The IO::Handle package is a base class for input-output things, so it handles a lot more than just files.

[*] Have you ever wondered why there is no comma after the filehandle portion of the print? It's really the indirect object notation (which we haven't mentioned yet, unless you've read the whole book before you read the footnotes, like we told you to do in the preface!).

Unless you're creating new IO modules, you probably shouldn't use IO::Handle directly. Instead, use some of the handy modules built on top of it. We haven't told you about object-oriented programming (OOP) yet (it's in Chapter 11, so we almost have), but in this case, you just have to follow the example in its documentation.

Some of these modules do some of the same things that we can already do with Perl's built-in open (depending on which version of Perl we have), but they can be handy when we want to decide as late as possible which module should handle input or output. Instead of using the built-in open, we use the module interface. To switch the behavior, we simply change the module name. Since we've set up our code to use a module interface, it's not that much work to switch modules.

8.4.1. IO::File

The IO::File module subclasses IO::Handle to work with files. It comes with the standard Perl distribution, so you should already have it. There are a variety of ways to create an IO::File object.

We can create the filehandle reference with the one-argument form of the constructor. We check the result of the operation by looking for a defined value in the filehandle reference variable.

use IO::File;

my $fh = IO::File->new( '> castaways.log' )
        or die "Could not create filehandle: $!";

If you don't like that (for the same reasons as regular open), use one of the other calling conventions. The optional second argument is the filehandle mode.[Section 8.4.  IO::Handle]

[Section 8.4.  IO::Handle] These are the ANSI C fopen mode strings. You can also use these with the built-in open. Indeed, IO::File uses the built-in open behind the scenes.

my $read_fh  = IO::File->new( 'castaways.log', 'r' );

my $write_fh = IO::File->new( 'castaways.log', 'w' );

Using a bit mask as the mode allows for more granular control. The IO::File module supplies the constants.

my $append_fh = IO::File->new( 'castaways.log', O_WRONLY|O_APPEND );

Besides opening named files, we might want to open an anonymous temporary file. On systems that support this sort of thing, we simply create the new object to get a read-write filehandle.

my $temp_fh = IO::File->new_tmpfile;

As before, Perl closes these files when the scalar variable goes out of scope, but if that's not enough, we do it ourselves explicitly.

$temp_fh->close;

undef $append_fh;

8.4.2. Anonymous IO::File Objects

If we don't put our IO::File object in a simple scalar variable, some operations require a slightly modified syntax to work. For example, we want to copy every file matched by the glob pattern of *.input to a corresponding file whose suffix is .output, but do it in parallel. First, we open all the files, both inputs and outputs:

my @handlepairs;

foreach my $file ( glob( '*.input' ) ) {
        (my $out = $file) =~ s/\.input$/.output/;
        push @handlepairs, [
                (IO::File->new('<$file') || die),
                (IO::File->new('>$out') || die),
        ];
}

Now we have an array of references to arrays, each element of which is an IO::File object. Now, let's pump the data from the input files to the output files.

while (@handlepairs) {
  @handlepairs = grep {
    if (defined(my $line = $_->[0]->getline)) {
      print { $_->[1] } $line;
    } else {
      0;
    }
  } @handlepairs;
}

As long as we have pairs, we keep passing the list through the grep structure:

@handlepairs = grep { CONDITION } @handlepairs;

On each pass, only the handle pairs that evaluate as true in the grep CONDITION survive. Inside, we take the first element of each pair and try to read from it. If that's successful, write that line to the second element of the pair (the corresponding output handle). If the print is successful, it returns true, which lets grep know that we want to keep that pair. If either the print fails or the getline returns undef, the grep sees the false value as an indication to discard that pair. Discarding the pair automatically closes both filehandles. Cool!

Note that we can't use the more traditional filehandle read or filehandle print operations, because the reading and writing filehandles weren't in a simple scalar variable. We can rewrite that loop to see if copying the handles is easier:

while (@handlepairs) {
  @handlepairs = grep {
    my ($IN, $OUT) = @$_;
    if (defined(my $line = <$IN>)) {
      print $OUT $line;
    } else {
      0;
    }
  } @handlepairs;
}

This scenario is arguably better. Most of the time, simply copying the complexly referenced value into a simple scalar is easier on the eyes. In fact, another way to write that loop is to get rid of the ugly if structure:

while (@handlepairs) {
  @handlepairs = grep {
    my ($IN, $OUT) = @$_;
    my $line;
    defined($line = <IN>) and print $OUT $line;
  } @handlepairs;
}

As long as someone understands that and is a partial evaluator and that print returns true when everything is okay, this is a fine replacement. Remember the Perl motto: "There's more than one way to do it" (although not all of them are equally nice or legitimate).

8.4.3. IO::Scalar

Sometimes we don't want to print to a file and would rather build up the output in a string. Some module interfaces don't give us that option, so we have to make it look like we are printing to a file by using a filehandle. We might also want to build up our content before we write it to a file so we can encrypt it, compress it, or send it as email directly from your program.

The IO::Scalar module uses the magic of tie behind the scenes to give us a filehandle reference that appends to a scalar. This module doesn't come with the standard Perl distribution, so you'll have to install it yourself most likely.

use IO::Scalar;

my $string_log = '';
my $scalar_fh = IO::Scalar->new( \$string_log );

print $scalar_fh "The Howells' private beach club is closed\n";

Now our log message ends up in the scalar variable $string_log instead of a file. What if we want to read from our logfile, though? We do the same thing. In this example, we create $scalar_fh just as we did before, then read from it with the line input operator. In our while loop, we'll extract the log messages that contain Gilligan (which is probably most of them, since he's always part of the mess):

use IO::Scalar;

my $string_log = '';
my $scalar_fh = IO::Scalar->new( \$string_log );

while( <$scalar_fh> ) {
        next unless /Gilligan/;
        print;
        }

As of Perl version 5.8, we can do this directly in Perl without using IO::Scalar.

open( my $fh, '>>', \$string_log )
        or die "Could not append to string! $!";

8.4.4. IO::Tee

What if we want to send output to more than one place at a time? What if we want to send it to a file and save it in a string at the same time? Using what we know already, we'd have to do something like this:

my $string = '';

open my $log_fh, '>>', 'castaways.log'
        or die "Could not open castaways.log;
open my $scalar_fh, '>>', \$string;

my $log_message = "The Minnow is taking on water!\n"
print $log_fh    $log_message;
print $scalar_fh $log_message;

Of course, we could shorten that a bit so we only have one print statement. We use the foreach control structure to iterate through the filehandle references, store each in $fh in turn, and print to each one.

foreach my $fh ( $log_fh, $scalar_fh ) {
        print $fh $log_message;
}

That's still a bit too much work. In the foreach, we had to decide which filehandles to include. What if we could just define a group of filehandles that answered to the same name? Well, that's what IO::Tee does for us. Imagine it like a tee connector on a bilge output pipe; when the water gets to the tee, it can flow it two different directions at the same time. When our output gets to IO::Tee, it can go to two (or more) different channels at the same time. That is, IO::Tee multiplexes output. In this example, the castaways' log message goes to both the logfile and the scalar variable.

use IO::Tee;

$tee_fh = IO::Tee->new( $log_fh, $scalar_fh );

print $tee_fh "The radio works in the middle of the ocean!\n";

That's not all, though. If the first argument to IO::Tee is an input filehandle (the succeeding arguments must be output filehandles), we can use the same teed filehandle to read from input and write to the output. The source and destination channels are different, but we get to treat them as a single filehandle.

use IO::Tee;

$tee_fh = IO::Tee->new( $read_fh, $log_fh, $scalar_fh );

# reads from $read_fh
my $message = <$tee_fh>;

# prints to $log_fh and $scalar_fh
print $tee_fh $message;

The $read_fh doesn't have to be connected to a file, either. It might also be connected to a socket, a scalar variable, an external command's output,[*] or anything else we can dream up.

[*] You can create readable filehandles to external commands with IO::Pipe.


Previous
Table of Contents
Next
© 2000- NIV