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

Generally, is it considered good Perl interface style for a function to accept either a file name or a handle? If so, what is the best way to implement it?

Test program follows.

use strict; ### DoFile. Test passing either a file name or a handle. ### Return the file contents. sub DoFile { my $fh = shift; local $/; if (UNIVERSAL::isa($fh, 'GLOB') || UNIVERSAL::isa(\$fh, 'GLOB')) { warn "DoFile: param is a glob\n"; return <$fh>; } else { warn "DoFile: param is NOT a glob\n"; local *F; open(F, $fh) or die "error: open '$fh': $!"; my $s = <F>; close(F) or die "error: close '$fh': $!"; return $s; } } my $file = 'f.tmp'; # test file warn "*** Case 1: pass a GLOB\n"; open(G, $file) or die "error: open $file: $!"; warn "contents: '" . DoFile(*G) . "'\n"; close(G) or die "close $file failed: $!"; warn "*** Case 2: pass a GLOB reference\n"; open(G, $file) or die "error: open $file: $!"; warn "contents: '" . DoFile(\*G) . "'\n"; close(G) or die "close $file failed: $!"; warn "*** Case 3: pass an autovivified scalar\n"; if ($] < 5.006) { warn "skip, perl version less than 5.006\n"; } else { open(my $fh, $file) or die "error: $file open: $!"; warn "contents: '" . DoFile($fh) . "'\n"; } warn "*** Case 4: pass an IO::File handle\n"; { require IO::File; my $fh = IO::File->new(); $fh->open($file) or die "error: open $file: $!"; warn "contents: '" . DoFile($fh) . "'\n"; } warn "*** Case 5: pass a file name\n"; warn "contents: '" . DoFile($file) . "'\n";

Replies are listed 'Best First'.
Re: Function that accepts either a file name or a handle
by Anonymous Monk on Apr 21, 2004 at 03:43 UTC

    In such situations, I find it best to use named parameters to explicitely choose which is being passed:

    sub doFile { my (%param) = @_; if ( defined($param{'filename'}) ) { warn( "We were passed a filename.\n" ); } elsif ( defined($param{'filehandle'}) ) { warn( "We were passed a filehandle.\n" ); } else { die( "We did not get a filename or filehandle.\n" ); } } # this for a filename: doFile( filename => 'foobar.qux' ); # or this for a filehandle: doFile( filehandle => \*DATA ); # or filehandle => $fh
Re: Function that accepts either a file name or a handle
by ysth (Canon) on Apr 21, 2004 at 03:55 UTC
    To work like perl's builtins, you need to use a * prototype to take a bareword or a scalar containing a string handle name or a glob or a globref or an ioref (or undef, but that's much harder to duplicate) and turn it into a usable handle, qualifying barewords with the caller's package. There is some advice on this in perlsub under Prototypes.
Re: Function that accepts either a file name or a handle
by jmcnamara (Monsignor) on Apr 21, 2004 at 06:10 UTC

    It is quite hard to check for all of the possible filehandles that could be passed to a function. In the Scalar::Util module there is a function called openhandle() that can be used to check whether the function argument is a valid filehandle.

    As a less rigourous solution you could just use ref to check if the argument is a reference and then presume a filehandle. It will depend on your application.

    --
    John.

      The heart of Scalar::Util's openhandle() is

      defined(fileno $arg)

      which was my first thought. It goes a bit further to try to deal with tied file handles (that you might be able to read from, etc. even though they don't directly contain an open file descriptor).

      If one wants to do something other than read/write the handle (for example: stat or select), then I'd just use defined(fileno $arg) (since stat and select aren't going to work on tied handles anyway).

      My second thought was that if one just wants to read from the handle, can might offer a simple and high-quality solution. I didn't produce a working example of that because I decided my third thought was even better in that case...

      eval{<$arg>} was my third thought. But testing reminded me that <...> doesn't give you any reasonable way to distinguish "end of file" from "error reading"1.

      1 I first discovered this when writing a book on Perl. No, it never got finished. The editor had health problems, the publisher didn't follow through, then I no longer had time.

      - tye        

        eval{<$arg>} was my third thought. But testing reminded me that <...> doesn't give you any reasonable way to distinguish "end of file" from "error reading".

        I don't understand how eval could be employed since the following program does not die when you read from <$fh>:

        my $fh = 'f.tmp'; my $s = <$fh>; print "i am here: s='$s'\n";

        That is, there is nothing to catch.

Re: Function that accepts either a file name or a handle
by runrig (Abbot) on Apr 21, 2004 at 16:55 UTC
    In File::Copy, this is used:
    my $from_a_handle = (ref($from) ? (ref($from) eq 'GLOB' || UNIVERSAL::isa($from, 'GLOB') || UNIVERSAL::isa($from, 'IO::Handle')) : (ref(\$from) eq 'GLOB'));

    I'm not sure why both isa 'GLOB' and isa 'IO::Handle' are checked, maybe somewhere there's a package that inherits from IO::Handle, but returns something other than a blessed glob ref (and so would probably have to override most all of the methods)? Or maybe someone was just being redundant :-)