views:

116

answers:

3

I have some subroutines that I call like this myWrite($fileName, \@data). myWrite() opens the file and writes out the data in some way. I want to modify myWrite so that I can call it as above or with a filehandle as the first argument. (The main reason for this modification is to delegate the opening of the file to the calling script rather than the module. If there is a better solution for how to tell an IO subroutine where to write, i'd be glad to hear it.)

In order to do this, I must test whether the first input var is a filehandle. I figured out how to do that by reading this question.

Now here's my question: I also want to test whether I can write to this filehandle. I can't figure out how to do that.

Here's what I want to do:

sub myWrite {
  my ($writeTo, $data) = @_;
  my $fh;
  if (isFilehandle($writeTo)) { # i can do this
    die "you're an immoral person\n" 
      unless (canWriteTo($writeTo)); # but how do I do this?
    $fh = $writeTo;
  } else {
    open $fh, ">", $writeTo;
  }
  ...
}

All I need to know is if I can write to the filehandle, though it would be nice to see some general solution that tells you whether you're filehandle was opened with ">>" or "<", or if it isn't open, etc.

(Note that this question is related but doesn't seem to answer my question.)

A: 

The -w operator can be used to test whether a file or a filehandle is writeable

open my $fhr, '<', '/etc/passwd' or die "$!";
printf("%s read from fhr\n", -r $fhr ? 'Can' : "Can't");
printf("%s write to fhr\n",  -w $fhr ? 'Can' : "Can't");

open my $fhw, '>', '/tmp/test' or die "$!";
printf("%s read from fhw\n", -r $fhw ? 'Can' : "Can't");
printf("%s write to fhw\n",  -w $fhw ? 'Can' : "Can't");

Output:

Can read from fhr
Can't write to fhr
Can read from fhw
Can write to fhw
Grant McLean
Not sure that this is right. I think this just tests whether the filehandle has opened a writeable file, not whether the filehandle itself is writeable. Try your first example with a file you have permission to write.
mobrule
mobrule is correct. `-w` tests whether the _file_ to which a filehandle is open is writable, not whether the filehandle has been opened in write mode.
cjm
Well that explains why the second filehandle appears to be readable (despite only being opened in write mode) which I must admit did strike me as odd.
Grant McLean
+6  A: 

Still experimenting with this, but maybe you can try a zero-byte syswrite to a filehandle and check for errors:

open A, '<', '/some/file';
open B, '>', '/some/other-file';

{
    local $! = 0;
    my $n = syswrite A, "";
    # result: $n is undef, $! is "Bad file descriptor"
}
{
    local $! = 0;
    my $n = syswrite B, "";
    # result: $n is 0, $! is ""
}

fcntl looks promising too. Your mileage may vary, but something like this could be on the right track:

use Fcntl;
$flags = fcntl HANDLE, F_GETFL, 0;  # "GET FLags"
if (  ($flags & O_ACCMODE) & (O_WRONLY|O_RDWR) ) {
    print "HANDLE is writeable ...\n"
}
mobrule
fcntl seems to be the solution i was after, though it reminds me why i was never a fan of C.
flies
This really ought to be an API on IO::Handle.
Ether
+3  A: 

It sounds like you are trying to reinvent exception handling. Don't do that. There are lots of potential errors besides being handed a write-only handle. How about being handed a closed handle? A handle with an existing error?

mobrule's method with use Fcntl; correctly determines the flags on a filehandle, but this does not generally handle errors and warnings.

If you want to delegate to the caller the responsibility of opening the file, delegate to the caller the appropriate handling of exceptions. This allows the caller to choose the appropriate response. The vast majority of times, it will be either to die or warn or fix the offending code that handed you a bad handle.

There are two way to handle exceptions on a file handle passed to you.

First, if you can look at TryCatch or Try::Tiny on CPAN and use that method of exception handling. I use TryCatch and it is great.

A second method is use eval and catch the appropriate error or warning after the eval is finished.

If you attempt to write to a read-only file handle, it is a warning that is generated. Catch the warning that is generated from your attempted write and you can then return success or failure to the caller.

Here is an example:

use strict; use warnings;

sub perr {
    my $fh=shift;
    my $text=shift;
    my ($package, $file, $line, $sub)=caller(0);
    my $oldwarn=$SIG{__WARN__};
    my $perr_error;

    {
        local $SIG{__WARN__} = sub { 
            my $dad=(caller(1))[3];
            if ($dad eq "(eval)" ) {
                $perr_error=$_[0];
                return ;
            }   
            oldwarn->(@_);
        };
        eval { print $fh $text }; 
    }    

    if(defined $perr_error) {
        my $s="$sub, line: $line";
        $perr_error=~s/line \d+\./$s/ ;
        warn "$sub called in void context with warning:\n" .  
             $perr_error 
             if(!defined wantarray);
        return wantarray ? (0,$perr_error) : 0;
    }
    return wantarray ? (1,"") : 1;
}

my $fh;
my @result;
my $res;
my $fname="blah blah file";

open $fh, '>', $fname;

print "\n\n","Successful write\n\n" 
     if perr $fh, "opened by Perl and writen to...\n";

close $fh;

open $fh, '<', $fname;

# void context:
perr $fh, "try writing to a read-only handle";

# scalar context:
$res=perr $fh, "try writing to a read-only handle";


@result=perr $fh, "try writing to a read-only handle";
if  ($result[0]) {
   print "SUCCESS!!\n\n";
} else {
    print "\n","I dunno -- should I die or warn this:\n";
    print $result[1];
}   

close $fh;
@result=perr $fh, "try writing to a closed handle";
if  ($result[0]) {
   print "SUCCESS!!\n\n";
} else {
    print "\n","I dunno -- should I die or warn this:\n";
    print $result[1];
}

The output:

Successful write

main::perr called in void context with warning:
Filehandle $fh opened only for input at ./perr.pl main::perr, line: 49

I dunno -- should I die or warn this:
Filehandle $fh opened only for input at ./perr.pl main::perr, line: 55

I dunno -- should I die or warn this:
print() on closed filehandle $fh at ./perr.pl main::perr, line: 64
drewk
Most modern people prefer `Try::Tiny` to `TryCatch`. Consider that.
Randal Schwartz
@Randal Schwartz: I will try Try::Tiny. I think you still have to redirect $SIG{__WARN__} for TryCatch though. Does either "catch" on warnings alone? I guess I could try it....
drewk
@Randal Schwartz: I did try Try::Tiny. It does not catch warnings. Only fatal errors.
drewk
Try::Tiny is small and elegant, but it doesn't allow catching various types of exceptions with type constraints like TryCatch does -- both have uses in Modern Perl.
Ether