[ Team LiB ] Previous Section Next Section

Recipe 7.5 Storing Filehandles into Variables

7.5.1 Problem

You want to use a filehandle like a normal variable so you can pass it to or return it from a function, store it in a data structure, and so on.

7.5.2 Solution

The easiest way to get a filehandle into a variable is to have open put it there for you:

open(my $fh, "<", $filename)      or die "$0: can't open $filename: $!";

To store named filehandles into a variable or pass them into or out of a function, use typeglob notation (*FH):

$variable = *FILEHANDLE;        # save in variable
subroutine(*FILEHANDLE);        # or pass directly

sub subroutine {
    my $fh = shift;
    print $fh "Hello, filehandle!\n";
}

7.5.3 Discussion

If you pass an undefined scalar variable as the first argument to open, Perl allocates an anonymous typeglob and stores a reference to that typeglob in that scalar, effectively creating filehandles on demand. Like all other references, autovivified filehandles are subject to garbage collection, so this code doesn't leak a filehandle:

{
    open(my $fh, "< /etc/motd") or die;
    local $/;          # slurp mode
    $text = <$fh>;
}

When Perl reaches the end of the block, $fh goes out of scope. As explained earlier in the Introduction, because that variable contained the last reference to the anonymous filehandle created by open, the variable is garbage collected and the filehandle implicitly closed.

Autovivified filehandles, being anonymous and already held in variables, don't help you to understand how to pass named filehandles as function parameters or store them in variables, including elements of arrays or hashes. By named filehandles, we mean those of the form FH, including all predefined handles, such as STDIN and ARGV. So let's look at what FH is and how to extract a scalar value from it to use for all of those things.

Named filehandles used in:

print STDERR "stuff\n";
$input = <STDIN>;
open(TTY, "+<", "/dev/tty");
if (eof(ARGV)) { .... }

are names, not variables. They're like subroutines in that way. This makes them inconvenient to pass around or store into data structures. Assuming you follow the prudent advice to compile all your code under use strict by default, normally you can't get away with this:

$fh = SOMEHANDLE;
somefunc(SOMEHANDLE);

because, absent declarations to the contrary, SOMEHANDLE is in both of these cases an unquoted string, which is forbidden by use strict. Even if you aren't using strict subs, you'll get into trouble if you try to pass your handle into a subroutine that was compiled under strict refs or in a different package than the calling code was compiled in.

The four named handles (STDERR, STDIN, TTY, and ARGV) we showed earlier didn't require special handling, but not because they are built-ins themselves; TTY, in fact, is not. Rather, they were okay because the built-in operations using them as arguments are all prototyped to take a filehandle argument.

So you must do one of two things. You could use a prototype for the function as explained in Recipe 7.6. Otherwise, you must use something that Perl will accept in lieu of a filehandle name. Acceptable substitutes include strings, typeglobs, references to typeglobs, and an I/O object, all of which may be stored into variables or passed into a function for later use as indirect filehandles.

somefunc(   SOMEHANDLE     );       # only w/ somefunc(*) proto
somefunc(  "SOMEHANDLE"    );       # an quoted string
somefunc(  *SOMEHANDLE     );       # a typeglob
somefunc( \*SOMEHANDLE     );       # ref to a typeglob
somefunc(  *SOMEHANDLE{IO} );       # an I/O object

Using a quoted string for the named handle has potential problems, as already explained, although this can work if the code is careful enough (again, see the next recipe). Better to use typeglob notation, either directly using *SOMEHANDLE or by reference using \*SOMEHANDLE:

somefunc(*SOMEHANDLE);
$fh = *SOMEHANDLE;  # or indirectly via a variable
somefunc($fh);

print $fh "data\n";

Typeglob notation spares you quoting or qualifying the handle name. It may help to conceptualize the asterisk as the type symbol for a filehandle. Like the little colored balls from high school chemistry that stood for atomic particles, it's not really true, but it is a convenient mental shorthand. By the time you understand where this model breaks down, you won't need it anymore.

If you assign any of the four alternate forms for named filehandles into a scalar variable, you can use that variable as an indirect filehandle wherever you would use a named filehandle.

However, complex expressions and subscripts into hashes or arrays cannot be used directly with built-ins like print, printf, or the line input operator. These are syntactically illegal and won't even compile:

@fd = (*STDIN, *STDOUT, *STDERR);
print $fd[1] "Type it: ";                           # WRONG
$got = <$fd[0]>                                     # WRONG
print $fd[2] "What was that: $got";                 # WRONG

With print and printf, you can get around this by using a block, returning an expression where you would place the filehandle:

print  { $fd[1] } "funny stuff\n";
printf { $fd[1] } "Pity the poor %x.\n", 3_735_928_559;
Pity the poor deadbeef.

That block is a proper block in all senses, so you can put more complicated code there. This sends the message out to one of two places:

$ok = -x "/bin/cat";
print { $ok ? $fd[1] : $fd[2]  } "cat stat $ok\n";
print { $fd[ 1 + ($ok || 0) ]  } "cat stat $ok\n";

This is so-called "indirect object" notation, discussed at length in Chapter 13. This restriction against using anything but simple scalar variables in the indirect object slot holds true for any sort of object. As with user-created objects, infix arrow notation avoids syntactic snafus here. If you have the IO::Handle module loaded, or anything that inherits from it, use an expression that produces the filehandle as though it were a proper object to invoke methods from that class:

$fd[1]->print("funny stuff\n");
($ok ? $fd[1] : $fd[2])->print("cat stat $ok\n");

This approach of treating print and printf like object methods calls won't work for the line input operator. Assuming you've been storing typeglobs in your structure as we did previously, the built-in readline function reads records just as <FH> does. Given the preceding initialization of @fd, this would work:

$got = readline($fd[0]);

or, with IO::Handle available, you can use the getline method:

$got = $fd[0]->getline( );

IO::Handle doesn't replace the readline function using just one method—it uses two, one per context. If you prefer readline's context-dependent behavior, you could always do this, adding something to the class on the fly:

sub IO::Handle::readline {
    my $fh = shift;
    if (wantarray) {
        return $fh->getlines( );
    } else {
        return $fh->getline( );
    }
}

7.5.4 See Also

The open function in perlfunc(1) and in Chapter 29 of Programming Perl; Recipe 7.1; the documentation with the standard IO::Handle module (also in Chapter 32 of Programming Perl); and the "Typeglobs and Filehandles" sections of Chapter 2 of Programming Perl

    [ Team LiB ] Previous Section Next Section