Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?

Accepting either a filename or a handle

by fizbin (Chaplain)
on Jun 18, 2004 at 20:59 UTC ( #368062=perlquestion: print w/replies, xml ) Need Help??
fizbin has asked for the wisdom of the Perl Monks concerning the following question:

So I'm writing a little routine that takes three parameters - a string, an input file name, and an output file name - and it occurs to me that there are times when I don't want to pass an output file name to this procedure, but rather that I want to pass an already open filehandle or an IO::Handle object.

However, I still need to be able to accept a simple filename if given one. So how do I determine what I've been passed, and then use the filehandle?

-- @/=map{[/./g]}qw/.h_nJ Xapou cets krht ele_ r_ra/; map{y/X_/\n /;print}map{pop@$_}@/for@/

Replies are listed 'Best First'.
Re: Accepting either a filename or a handle
by shemp (Deacon) on Jun 18, 2004 at 21:04 UTC
    if ( ref $thing eq 'GLOB' ) { # its a filehandle } elsif { ! ref $thing ) { # its a scalar - the filename hopefully } else { die 'invalid thing : $thing\n"; }
Re: Accepting either a filename or a handle
by eyepopslikeamosquito (Chancellor) on Jun 19, 2004 at 00:47 UTC
Re: Accepting either a filename or a handle
by cchampion (Curate) on Jun 18, 2004 at 21:12 UTC

    What about named parameters?

    sub doit { my %options = @_; if ($options{filename} ) { #use a filename } elsif ( $options{filehandle} ) { # use a filehandle } else { die "won't work"; } } doit( filename => 'somefile'); doit( filehandle => $fh );
Re: Accepting either a filename or a handle
by runrig (Abbot) on Jun 18, 2004 at 22:39 UTC
    Look at the source code for File::Copy::copy, where it determines whether or not the arguments passed are handles.
Re: Accepting either a filename or a handle
by hv (Parson) on Jun 19, 2004 at 13:22 UTC

    There are several possible approaches to this. One is to assume that anything passed is either a simple string or an appropriate handle, and just choose between the two:

    if (ref $param) { # it isn't a simple string, assume it is a filehandle $fh = $param; } else { open $fh, "<", $param or die "$param: $!"; }

    A second approach is to require one of a set of specific types, but this disallows inherited types:

    my $type = ref $param; if (!$type) { # it's a simple scalar } elsif ($type eq 'GLOB') { # an unblessed GLOB ref } elsif ($type eq 'IO::Handle') { # it's a non-overloaded IO::Handle } else { ... }

    (When using this approach or some of the others, it is also worth being aware of Scalar::Util which has methods blessed() and reftype() for finer distinctions than ref() offers.)

    A third approach is to look for inherited types using isa():

    if (!ref $param) { # scalar } elsif (ref($param) eq 'GLOB') { # GLOB } elsif ($param->isa('IO::Handle')) { # is, or inherits from, IO::Handle } else { ... }

    Yet another possibility is to test for the features you need with can(); say the only thing you need to be able to do with it is readline you could say:

    if (!ref $param) { # scalar } elsif ($param->can('readline')) { # who knows what it is? But it supports a readline method ... } else { ... }

    The various approaches all have different benefits and drawbacks, and which suits you best will depend on the specifics of your application. In general, though, I prefer the first: check as little as possible early on, and let the later code fail with a message such as:

    Can't locate method 'readline' via package 'IO::StrangeHandle'

    In particular, this approach is the least likely to stop someone using your routine with some object that they know should work, but due to some strangeness in how it describes itself might fail to match one of the other checks.


Re: Accepting either a filename or a handle
by belden (Friar) on Jun 19, 2004 at 20:25 UTC

    Alternatively, if you don't like the ref solutions, you can use one of perl's Obscure Open Tricks

    sub your_function { my ( $string, $in_fh, $out ) = @_; local *OUTFH; # opens $out if it's a filename # dupes $out if it's a filehandle open OUTFH, ">$out" or die "$out: $!\n"; # do whatever with $in_fh and $string while( <$in_fh> ) { /$string/o and print OUTFH "$string: $_" } # close OUTFH: either we opened it or we duped it # Either way we don't need it any more. close OUTFH; }

    Then your_function() just relies on its caller to do the right thing. If you want to pass your_function() a filename to take as output, then you'd call it as you'd expect:

    your_function( $string, \*INPUT, '/tmp/somefile' );

    On the other hand, if you want to pass your_function() an already opened filehandle, and use that for output, then you'd call it as:

    # print to STDOUT instead of to disk your_function( $string, \*INPUT, "&STDOUT" ); # print to already-opened filehandle open OUTPUT, '> /tmp/somefile' or die "/tmp/somefile: $!\n"; your_function( $string, \*INPUT, "&OUTPUT" );

    For more info on this, check out Obscure Open Tricks in perlopentut. There's a recommendation in there that you qualify the package name where OUTPUT is located, which I've ommitted in the code above.

    One thing worth noting is that the line

    open OUTFH, ">$out" or die "$out: $!\n";

    Must appear as written (i.e. with no spaces between the > and $out). Avoid these following variations, since they don't do the same thing as the line above

    # wrong: given, eg, &STDOUT, creates a file named '&STDOUT' open OUTFH, "> $out" or die "$out: $!\n"; # wrong: also creates, eg, '&STDOUT' open OUTFH, '>', $out or die "$out: $!\n";
Re: Accepting either a filename or a handle
by broquaint (Abbot) on Jun 21, 2004 at 03:57 UTC
    There's a function in Module::Locate that determines whether a scalar acts like a filehandle or not. So with that you could do something like this in your function
    use Module::Locate 'acts_like_fh'; sub fizbin { my($str, $infh, $outfh) = @_; -e $_ or acts_like_fh $_ or die "Invalid file name/handle '$_'" for $infh, $outfh; ... }


Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://368062]
Approved by PERLscienceman
Front-paged by grinder
[jedikaiti]: Eclipse Shiney!

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (3)
As of 2017-08-23 16:09 GMT
Find Nodes?
    Voting Booth?
    Who is your favorite scientist and why?

    Results (354 votes). Check out past polls.