Perl Cookbook

Perl CookbookSearch this book
Previous: 7.15. Determining the Number of Bytes to ReadChapter 7
File Access
Next: 7.17. Caching Open Output Filehandles
 

7.16. Storing Filehandles in Variables

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.

Solution

If you already have a regular symbolic filehandle like STDIN or LOGFILE, use the typeglob notation, *FH. This is the most efficient approach.

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

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

If you want anonymous filehandles, see the return_fh function below, or use the new method from the IO::File or IO::Handle module, store that in a scalar variable, and use it as though it were a normal filehandle:

use FileHandle;                   # make anon filehandle
$fh = FileHandle->new();

use IO::File;                     # 5.004 or higher
$fh = IO::File->new();

Discussion

You have many choices for passing filehandles into a subroutine or storing them in a data structure. The simplest and fastest way is through the typeglob notation, *FH. It may help you 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.

That works cleanly for simple things, but what if you wanted to make an array of filehandles whose names you didn't know? As shown in Chapter 11, References and Records, generating anonymous arrays, hashes, and even functions on the fly can prove extremely convenient. It would be nice to be able to do the same with filehandles. That's where the IO modules come in.

You can generate an anonymous handle with the IO::Handle or IO::File module's new method. This returns a filehandle you can pass to subroutines, store in arrays, and use however you would use a named filehandle's typeglob  - plus more. You can also use those modules as object classes for inheritance purposes because the return values from the new constructor are fully fledged objects, complete with available method calls.

You can use these as indirect filehandles, which saves you the trouble of thinking up filehandle names. Instead, you think up names to store the anonymous filehandles in.

To capture the typeglob from a named filehandle, prefix it with *:

$fh_a = IO::File->new("< /etc/motd")    or die "open /etc/motd: $!";
$fh_b = *STDIN;
some_sub($fh_a, $fh_b);

This isn't the only way, but it is the simplest and most convenient. Its only limitation is that you can't bless it to turn it into an object. To do this, you must bless a reference to a typeglob  - that's what IO::Handle does. Like typeglobs, references to typeglobs can be safely used as indirect filehandles, whether blessed or not.

To create and return a new filehandle from a function, do this:

sub return_fh {             # make anon filehandle
    local *FH;              # must be local, not my
    # now open it if you want to, then...
    return *FH;
}

$handle = return_fh();

A subroutine accepting a filehandle argument can either store the argument into a (preferably lexical) variable and use that as an indirect filehandle:

sub accept_fh {
    my $fh = shift;
    print $fh "Sending to indirect filehandle\n";
}

or it can localize a typeglob and use the filehandle directly:

sub accept_fh {
    local *FH = shift;
    print  FH "Sending to localized filehandle\n";
}

Both styles work with either IO::Handle objects or typeglobs of real filehandles:

accept_fh(*STDOUT);
accept_fh($handle);

Perl accepts many things as indirect filehandles (strings, typeglobs, and references to typeglobs), but unless you pass typeglobs or IO::Handle objects you may run into trouble. Strings ("LOGFILE" instead of *LOGFILE) require special finessing to work between packages, and references to typeglobs can't be usefully returned from functions.

In the preceding examples, we assigned the filehandle to a scalar variable before using it. That is because only simple scalar variables, not expressions or subscripts into hashes or arrays, can be used with built-ins like print, printf, or the diamond operator. These are 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 get around this by using a block and 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, 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 approach of treating print and printf like object methods calls doesn't work for the diamond operator, because it's a real operator, not just a function with a comma-less argument. Assuming you've been storing typeglobs in your structure as we did above, you can use the built-in function named readline to read a record just as <FH> does. Given the preceding initialization of @fd, this would work:

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

See Also

The open function in perlfunc (1) and in Chapter 3 of Programming Perl; Recipe 7.1; the documentation with the standard FileHandle module (also in Chapter 7 of Programming Perl); the "Typeglobs and Filehandles" section of Chapter 2 of Programming Perl and Chapter 2 of Programming Perl


Previous: 7.15. Determining the Number of Bytes to ReadPerl CookbookNext: 7.17. Caching Open Output Filehandles
7.15. Determining the Number of Bytes to ReadBook Index7.17. Caching Open Output Filehandles



Banner.Novgorod.Ru