【问题标题】:How can I test if I can write to a filehandle?如何测试是否可以写入文件句柄?
【发布时间】:2011-04-17 23:01:26
【问题描述】:

我有一些像 myWrite($fileName, \@data) 这样调用的子程序。 myWrite() 打开文件并以某种方式写出数据。我想修改myWrite 以便我可以像上面那样调用它 ,并将文件句柄作为第一个参数。 (此修改的主要原因是将文件的打开委托给调用脚本而不是模块。如果有更好的解决方案来告诉 IO 子例程在哪里写,我会很高兴听到它。 )

为了做到这一点,我必须测试第一个输入变量是否是文件句柄。我通过阅读this question 弄清楚了如何做到这一点。

现在这是我的问题:我还想测试我是否可以写入这个文件句柄。我不知道该怎么做。

这是我想做的:

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;
  }
  ...
}

我只需要知道我是否可以写入文件句柄,尽管很高兴看到一些通用的解决方案告诉你文件句柄是用“>>”还是“

(请注意,this question 是相关的,但似乎没有回答我的问题。)

【问题讨论】:

  • 为什么不好好记录你的模块,让调用者需要给你一个合法的可写句柄,如果传递的句柄不可写,那么它会死掉或优雅地处理错误?我错过了什么吗?
  • @drewk 在什么情况下死掉?如何识别错误?
  • 引用的代码可靠地确定标量值是否包含文件句柄!!
  • @tchrist 好吧,然后评论这些答案或发布一个。破碎的?修复它!

标签: perl file-io filehandle


【解决方案1】:

检测句柄的开放性

正如 Axeman 指出的那样,$handle->opened() 会告诉您它是否已打开。

use strict;
use autodie;
use warnings qw< FATAL all >;
use IO::Handle;
use Scalar::Util qw< openhandle >;

our $NULL = "/dev/null";
open NULL;
printf "NULL is %sopened.\n", NULL->opened() ? "" : "not ";
printf "NULL is %sopenhandled.\n", openhandle("NULL") ? "" : "not ";
printf "NULL is fd %d.\n", fileno(NULL);

生产

NULL is opened.
NULL is not openhandled.
NULL is fd 3.

如您所见,您不能使用Scalar::Util::openhandle(),因为它太愚蠢和错误。

打开手柄压力测试

正确的方法,如果您没有使用IO::Handle-&gt;opened,在以下简单的小三语脚本中演示:

eval 'exec perl $0 ${1+"$@"}'
               if 0;

use 5.010_000;
use strict;
use autodie;
use warnings qw[ FATAL all ];

use Symbol;
use IO::Handle;

#define exec(arg)
BEGIN { exec("cpp $0 | $^X") } #!/usr/bin/perl -P
#undef  exec

#define SAY(FN, ARG) printf("%6s %s => %s\n", short("FN"), q(ARG), FN(ARG))
#define STRING(ARG)  SAY(qual_string, ARG)
#define GLOB(ARG)    SAY(qual_glob, ARG)
#define NL           say ""
#define TOUGH        "hard!to!type"

sub comma(@);
sub short($);
sub qual($);
sub qual_glob(*);
sub qual_string($);

$| = 1;

main();
exit();

sub main { 

    our $GLOBAL = "/dev/null";
    open GLOBAL;

    my $new_fh = new IO::Handle;

    open(my $null, $GLOBAL);

    for my $str ($GLOBAL, TOUGH) {
        no strict "refs";
        *$str = *GLOBAL{IO};
    }

    STRING(  *stderr       );
    STRING(  "STDOUT"      );
    STRING(  *STDOUT       );
    STRING(  *STDOUT{IO}   );
    STRING( \*STDOUT       );
    STRING( "sneezy"       );
    STRING( TOUGH );
    STRING( $new_fh        );
    STRING( "GLOBAL"       );
    STRING( *GLOBAL        );
    STRING( $GLOBAL        );
    STRING( $null          );

    NL;

    GLOB(  *stderr       );
    GLOB(   STDOUT       );
    GLOB(  "STDOUT"      );
    GLOB(  *STDOUT       );
    GLOB(  *STDOUT{IO}   );
    GLOB( \*STDOUT       );
    GLOB(  sneezy        );
    GLOB( "sneezy"       );
    GLOB( TOUGH );
    GLOB( $new_fh        );
    GLOB(  GLOBAL        );
    GLOB( $GLOBAL        );
    GLOB( *GLOBAL        );
    GLOB( $null          );

    NL;

}

sub comma(@) { join(", " => @_) }

sub qual_string($) { 
    my $string = shift();
    return qual($string);
} 

sub qual_glob(*) { 
    my $handle = shift();
    return qual($handle);
} 

sub qual($) {
    my $thingie = shift();

    my $qname = qualify($thingie);
    my $qref  = qualify_to_ref($thingie); 
    my $fnum  = do { no autodie; fileno($qref) };
    $fnum = "undef" unless defined $fnum;

    return comma($qname, $qref, "fileno $fnum");
} 

sub short($) {
    my $name = shift();
    $name =~ s/.*_//;
    return $name;
} 

运行时产生的结果:

string    *stderr        => *main::stderr, GLOB(0x8368f7b0), fileno 2
string    "STDOUT"       => main::STDOUT, GLOB(0x8868ffd0), fileno 1
string    *STDOUT        => *main::STDOUT, GLOB(0x84ef4750), fileno 1
string    *STDOUT{IO}    => IO::Handle=IO(0x8868ffe0), GLOB(0x84ef4750), fileno 1
string   \*STDOUT        => GLOB(0x8868ffd0), GLOB(0x8868ffd0), fileno 1
string   "sneezy"        => main::sneezy, GLOB(0x84169f10), fileno undef
string   "hard!to!type"  => main::hard!to!type, GLOB(0x8868f1d0), fileno 3
string   $new_fh         => IO::Handle=GLOB(0x8868f0b0), IO::Handle=GLOB(0x8868f0b0), fileno undef
string   "GLOBAL"        => main::GLOBAL, GLOB(0x899a4840), fileno 3
string   *GLOBAL         => *main::GLOBAL, GLOB(0x84ef4630), fileno 3
string   $GLOBAL         => main::/dev/null, GLOB(0x7f20ec00), fileno 3
string   $null           => GLOB(0x86f69bb0), GLOB(0x86f69bb0), fileno 4

  glob    *stderr        => GLOB(0x84ef4050), GLOB(0x84ef4050), fileno 2
  glob     STDOUT        => main::STDOUT, GLOB(0x8868ffd0), fileno 1
  glob    "STDOUT"       => main::STDOUT, GLOB(0x8868ffd0), fileno 1
  glob    *STDOUT        => GLOB(0x8868ffd0), GLOB(0x8868ffd0), fileno 1
  glob    *STDOUT{IO}    => IO::Handle=IO(0x8868ffe0), GLOB(0x84ef4630), fileno 1
  glob   \*STDOUT        => GLOB(0x8868ffd0), GLOB(0x8868ffd0), fileno 1
  glob    sneezy         => main::sneezy, GLOB(0x84169f10), fileno undef
  glob   "sneezy"        => main::sneezy, GLOB(0x84169f10), fileno undef
  glob   "hard!to!type"  => main::hard!to!type, GLOB(0x8868f1d0), fileno 3
  glob   $new_fh         => IO::Handle=GLOB(0x8868f0b0), IO::Handle=GLOB(0x8868f0b0), fileno undef
  glob    GLOBAL         => main::GLOBAL, GLOB(0x899a4840), fileno 3
  glob   $GLOBAL         => main::/dev/null, GLOB(0x7f20ec00), fileno 3
  glob   *GLOBAL         => GLOB(0x899a4840), GLOB(0x899a4840), fileno 3
  glob   $null           => GLOB(0x86f69bb0), GLOB(0x86f69bb0), fileno 4

就是你测试打开文件句柄的方法!

但我相信这甚至不是你的问题。

不过,我觉得它需要解决,因为这里有太多不正确的解决方案来解决这个问题。人们需要睁大眼睛看看这些东西是如何工作的。请注意,Symbol 中的两个函数在必要时会使用 caller 的包——当然经常如此。

确定打开句柄的读/写模式

这个是您问题的答案:

#!/usr/bin/env perl

use 5.10.0;
use strict;
use autodie;
use warnings qw< FATAL all >;

use Fcntl;

my (%flags, @fh);
my $DEVICE  = "/dev/null";
my @F_MODES = map { $_ => "+$_" } qw[ < > >> ];
my @O_MODES = map { $_ | O_WRONLY }
        O_SYNC                          ,
                 O_NONBLOCK             ,
        O_SYNC              | O_APPEND  ,
                 O_NONBLOCK | O_APPEND  ,
        O_SYNC | O_NONBLOCK | O_APPEND  ,
    ;

   open($fh[++$#fh], $_, $DEVICE) for @F_MODES;
sysopen($fh[++$#fh], $DEVICE, $_) for @O_MODES;

eval { $flags{$_} = main->$_ } for grep /^O_/, keys %::;

for my $fh (@fh) {
    printf("fd %2d: " => fileno($fh));
    my ($flags => @flags) = 0+fcntl($fh, F_GETFL, my $junk);
    while (my($_, $flag) = each %flags) {
        next if $flag == O_ACCMODE;
        push @flags => /O_(.*)/ if $flags & $flag;
    }
    push @flags => "RDONLY" unless $flags & O_ACCMODE;
    printf("%s\n",  join(", " => map{lc}@flags));
}

close $_ for reverse STDOUT => @fh;

运行时会产生以下输出:

fd  3: rdonly
fd  4: rdwr
fd  5: wronly
fd  6: rdwr
fd  7: wronly, append
fd  8: rdwr, append
fd  9: wronly, sync
fd 10: ndelay, wronly, nonblock
fd 11: wronly, sync, append
fd 12: ndelay, wronly, nonblock, append
fd 13: ndelay, wronly, nonblock, sync, append

现在开心吗,施韦恩? ☺

【讨论】:

  • 是的。您知道 Stack Overflow 会将每条评论的 10% 发送给月球上饥饿的儿童吗?因此,请务必在您的评论中留下少于 15 个字符。想想 Mooninites。
  • 这条线的用途和作用:eval { $flags{$_} = main-&gt;$_ } for grep /^O_/, keys %::;
  • @Jakub 它会计算出当前系统的所有 O_* 标志名称和数字,并对它们进行哈希处理。
  • %:: (%main::) 是主符号表,main-&gt;$_ 是为了避免按名称调用子程序(这就是 constants 在 Perl 中的内容)(这需要 no strict 'refs';,不是吗?我理解正确吗?好技巧!
【解决方案2】:

仍在尝试,但也许您可以尝试对文件句柄进行零字节系统写入并检查错误:

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 看起来也很有希望。您的里程可能会有所不同,但这样的事情可能会走上正轨:

use Fcntl;
$flags = fcntl HANDLE, F_GETFL, 0;  # "GET FLags"
if (  ($flags & O_ACCMODE) & (O_WRONLY|O_RDWR) ) {
    print "HANDLE is writeable ...\n"
}

【讨论】:

  • fcntl 似乎是我所追求的解决方案,尽管它提醒了我为什么我从来不是 C 的粉丝。
  • 这确实应该是 IO::Handle 上的 API。
【解决方案3】:

如果您正在使用IO(并且您应该),那么$handle-&gt;opened 会告诉您句柄是否打开。可能需要更深入地了解它的模式。

【讨论】:

    【解决方案4】:

    听起来您正在尝试重新发明异常处理。不要那样做。除了被交给只写句柄之外,还有很多潜在的错误。给一个封闭的把手怎么样?存在错误的句柄?

    带有use Fcntl; 的mobrule 方法可以正确确定文件句柄上的标志,但这通常不能处理错误和警告。

    如果您想将打开文件的责任委托给调用者,请将适当的异常处理委托给调用者。这允许调用者选择适当的响应。绝大多数时候,要么死亡,要么警告或修复给你带来不好处理的有问题的代码。

    有两种方法可以处理传递给您的文件句柄的异常。

    首先,如果您可以查看 CPAN 上的 TryCatchTry::Tiny 并使用该异常处理方法。我使用 TryCatch,它很棒。

    第二种方法是使用eval 并在评估完成后捕获相应的错误或警告。

    如果您尝试写入只读文件句柄,则会生成警告。捕获您尝试写入时生成的警告,然后您可以将成功或失败返回给调用者。

    这是一个例子:

    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];
    }
    

    输出:

    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
    

    【讨论】:

    • 大多数现代人更喜欢Try::Tiny 而不是TryCatch。考虑一下。
    • @Randal Schwartz:我会试试 Try::Tiny。我认为你仍然需要为 TryCatch 重定向 $SIG{WARN} 。是否单独“抓住”警告?我想我可以试试......
    • @Randal Schwartz:我确实尝试过 Try::Tiny。它不会捕获警告。只有致命错误。
    • Try::Tiny 小巧而优雅,但它不允许像 TryCatch 那样使用类型约束捕获各种类型的异常——两者都在 Modern Perl 中使用。
    • TryCatch 已损坏,因为它所依赖的 Devel::Declare 已死。使用Nice::Try,它像其他编程语言一样真正完整地实现了try-catch块,包括catch、变量赋值和finally。
    【解决方案5】:

    -w 运算符可用于测试文件或文件句柄是否可写

    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");
    

    输出:

    Can read from fhr
    Can't write to fhr
    Can read from fhw
    Can write to fhw
    

    【讨论】:

    • 不确定这是否正确。我认为这只是测试文件句柄是否打开了可写文件,而不是文件句柄本身是否可写。使用您有权写入的文件尝试您的第一个示例。
    • mobrule 是正确的。 -w 测试打开文件句柄的 文件 是否可写,而不是文件句柄是否已以写入模式打开。
    • 嗯,这就解释了为什么第二个文件句柄似乎是可读的(尽管只在写入模式下打开),我必须承认这确实让我觉得很奇怪。
    猜你喜欢
    • 2011-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多