Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling

Sending filehandles?

by tamaguchi (Pilgrim)
on Feb 08, 2008 at 15:28 UTC ( #666990=perlquestion: print w/replies, xml ) Need Help??

tamaguchi has asked for the wisdom of the Perl Monks concerning the following question:

Suppose I open a file in a normal way like this:
 open(FILEHANDLE, "$filename") or die "Could not open file";
After this code has been run the filehandle hopefully is open but is it global? Is it possible to send it to a package or some other subrutine? Since the filehandle is not a normal variable, is there a some special syntax for sending it?

Replies are listed 'Best First'.
Re: Sending filehandles?
by almut (Canon) on Feb 08, 2008 at 15:37 UTC

    If you use the "new style" syntax (update: new as of v5.6.1, released about seven years ago — so not really all that new...)

    open(my $fh, "<", $filename) or die "Could not open file '$filename' +: $!";

    you can pass the filehandle (here $fh) around like any other variable.

Re: Sending filehandles?
by Tanktalus (Canon) on Feb 08, 2008 at 15:58 UTC

    Using the lexical form, as has already been mentioned, is considered better by most monks it appears (and I agree). However, to answer your literal question, the answer is "yes". Yes, it's global (though in your package). Other subroutines can use it as-is, calling it "FILEHANDLE". That, of course, means that if a subroutine opens another file using the FILEHANDLE glob, well, you run into problems. This is why you should always localise your filehandles before use - it doesn't prevent someone else from stepping on your filehandle, but it does prevent you from stepping on someone else's filehandle. If everyone does that, then there ends up being no squashing of handles.

    Next question is also a "yes" - it's possible. In fact, there are two ways. First is to take advantage of the fact that when you use "FILEHANDLE" with no sigil, it's treated as a bareword. And since there's no function by the name "FILEHANDLE" (all upper-case subs are frowned upon except as constants, so we should be ok here), perl treats it as the string "FILEHANDLE" Then it uses that string as the name of the variable to populate. Sneakily, it treats it as if it were in the caller's package space, so if you pass a string with the name of the handle to another package, you'll have to construct it fully, e.g.:

    #!/usr/bin/perl use strict; use warnings; Foo::handle(__PACKAGE__ . '::DATA'); package Foo; sub handle { my $handle = shift; my $data = join '', <$handle>; print $data; } __END__ Some text Blah blah
    This is the bad way of doing it. The second option is to use a reference to the real variable: *FILEHANDLE. So you can call a function like this: Foo::handle(\*FILEHANDLE) or, in my example:
    #!/usr/bin/perl use strict; use warnings; Foo::handle(\*DATA); package Foo; sub handle { my $handle = shift; my $data = join '', <$handle>; print $data; } __END__ Some text Blah blah
    Note that all the localisation requirements still hold with these - if Foo::handle were to open a new handle using your ::FILEHANDLE (directly or indirectly), it would still quash the filehandle it was working with. Better to use lexical variables. (I don't think you can use lexical variables with DATA, but that's probably ok, since it's a built-in and I doubt anyone would use that handle for anything else.)

Re: Sending filehandles?
by FunkyMonk (Chancellor) on Feb 08, 2008 at 15:38 UTC
    The "normal" way of achieving this is to use a lexical filehandle:
    open(my $FILEHANDLE, ">", $filename) or die "Could not open file: $!"; print $FILEHANDLE "This goes to \$filename, as normal"; mysub( $FILEHANDLE );

    I've used the 3-argument form of open, and included $! in die. That'll let perl tell you what the problem is rather than just "Could not open file".

    $FILEHANDLE can be passed to a subroutine, just like any other variable.

      I've ... included $! in die.

      You've only done half the job there :-). If you don't put both the filename and the OS error in the error message, the user can't diagnose the actual problem. OP's (unhelpful) error message:

      Could not open file
      Your (fractionally more helpful) error message:
      Could not open file: Permission denied
      open(my $FILEHANDLE, ">", $filename) or die "Cannot open $filename: $!";
      yields a helpful error message:
      Cannot open /dev/secret: Permission denied
      To be super-picky, it's "Cannot open", not "Can't open", or "Could not open", or any other variation. In English, at least.

        Ahhh, a useless spelling and/or grammar criticism. Which is, of course, wrong, as such things always are. "Can't open" is just fine. "Can not" needs a space in the middle.

        But a good point about spitting out the filename in the error message.

Re: Sending filehandles?
by shmem (Chancellor) on Feb 08, 2008 at 17:26 UTC
    Substituting a global file handle with a lexical has already been shown. But you can also pass around a typeglob. The syntax for that is:
    use strict; package Foo; sub line { my $fh = shift; return <$fh>; } package main; open FH, $file; # depending on the perl version, use 3 argument open my $line = Foo::line(*FH);

    update: that said, passing around a file handle is icky. You should reconsider your layout and encapsulate file operations into functions or methods. Make those callable from elsewhere, probably via an object.


    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
      But you can also pass around a typeglob.

      Sure, and you can also get the underlying file descriptor from the filehandle and pass that around and dup a new filehandle to the file descriptor, but don't do that.

      Seriously. Why?

        Well, er... it's... because you can? Not sure - TIMTOWTDI? Well, not all what can be done should be done. Updating node...


        _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                      /\_¯/(q    /
        ----------------------------  \__(m.====·.(_("always off the crowd"))."·
        ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}

      Better to pass a ref to the glob, \*FH, than *FH. 1) The file handles created by "open my ..." and by things like IO::Handle->new( ... ) are all references to globs so everybody will be getting something that they expect to get. 2) It is a pain to tell *FH from "*FH" and so code that is only slightly naive might not realize that you passed in a file handle (such as code that expects either a handle or a file name).

      And, yes, chromatic, I've certainly done that. For one, the "open my ..." trick isn't backward compatible (and doesn't even fail in a way that makes one likely to think "oh, that doesn't work on this version of Perl") and the alternatives that are backward compatible to the oldest deployed Perls in environments I've recently worked in are a bit obnoxious. For another, there are very few problems with using open FOO, ... in relatively small code, thanks in large part to package.

      - tye        

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://666990]
Approved by toolic
Front-paged by citromatik
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others meditating upon the Monastery: (4)
As of 2023-12-05 05:19 GMT
Find Nodes?
    Voting Booth?
    What's your preferred 'use VERSION' for new CPAN modules in 2023?

    Results (25 votes). Check out past polls.