[ Team LiB ] Previous Section Next Section

11.7 References to Filehandles

So far, you've seen references to scalars, arrays, hashes, and subroutines. Another important value type in Perl is the filehandle.

However, a filehandle isn't stored in a variable. The filehandle is the handle itself. You can't take a reference directly to a filehandle.[3] However, using the IO::File built-in class, you can create objects that act like filehandles within Perl. Here's a typical use:

[3] You can use the glob, take a reference to the glob, or take a reference to the I/O structure within a glob, but that's still not a reference to the filehandle.

use IO::File;

my $fh = IO::File->open("/etc/passwd")
  or die "constructor failed: $!";

while (<$fh>) {        # $fh acts like any filehandle
  print "a password line is $_";
}

close $fh;             # nearly all built-ins can use IO::File

Here, $fh is constructed using the open class method of IO::File, and then used in places where ordinarily you'd use a traditional (bareword) filehandle. Furthermore, you also get some additional methods:

if ($fh->opened) { ... } # file is open

$fh->blocking(0);       # make I/O be "non-blocking" if supported

The core built-in operations that use filehandles can all use an IO::File objects instead. If the IO::File object is within a simple scalar variable, you can always replace the filehandle with the scalar:

use IO::File;
my $fh = IO::File->new; # create unopened "filehandle" object

open $fh, ">my_new_file" or die "Cannot create: $!";
print $fh "$_\n" for 1..10;
close $fh;

An IO::File object automatically gets closed cleanly when destroyed, so you can simplify the previous code as:

use IO::File;
{
  my $fh = IO::File->open(">my_new_file")
    or die "Cannot create my_new_file: $!";
  print $fh, "$_\n" for 1..10;
}

As $fh goes out of scope, the filehandle is automatically closed. Nice.

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

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

Now you have an array of references to arrays, each element of which is an IO::File object. Let's pump the data:

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

As long as you have pairs, 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, you 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 you want to keep that pair. If either the print fails or the getline returned undef, the grep sees the false value as an indication to discard that pair. Discarding the pair automatically closes both filehandles. Cool!

Note that you 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. 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 OK, 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).

    [ Team LiB ] Previous Section Next Section