文件指针

时间:2015-06-29 07:47:32

标签: perl

这个简短的例子说明了我在Perl中遇到的一个问题。我们的想法是将stdin作为默认值处理或使用输入文件(如果已指定)。

#!/usr/bin/env perl

qx{echo "a file" > a};
qx{echo "b file" > b};
qx{echo "c file" > c};

process('a');
process('c');

sub process {
    my $name = shift;
    my $fp = *STDIN;            
    open $fp,  '<', $name if $name;    
    process('b') if $name eq 'a';

    print "Processing file '$name' (fp=$fp)\n";    
    print while(<$fp>);
}

我得到的输出是:

$ ./curious.pl
Processing file 'b' (fp=*main::STDIN)
b file
Processing file 'a' (fp=*main::STDIN)
Processing file 'c' (fp=*main::STDIN)
c file

应该是:

$ ./curious.pl
Processing file 'b' (fp=*main::STDIN)
b file
Processing file 'a' (fp=*main::STDIN)
a file
Processing file 'c' (fp=*main::STDIN)
c file

我可能错过了两件事:

  • 为什么$fp等于*main::STDIN而不是当前打开的文件?
  • 为什么不读取内容'a'

逻辑上,$fp是子例程的本地。它首先分配给*STDIN,然后由open更改为文件指针a。然后我处理b。当我返回到b的处理时,我仍然应该在a内指向$fp

我已经阅读here传递给open的处理程序必须是未定义的标量。但是,它似乎适用于bc

2 个答案:

答案 0 :(得分:5)

这与重新分配STDIN

有关
#!/usr/bin/env perl

use strict;
use warnings;
use Data::Dumper;

qx{echo "a file" > a};
qx{echo "b file" > b};
qx{echo "c file" > c};

process('a');
process('c');

sub process {
    my $name = shift;
    print "Starting process with $name from ", scalar caller(), " \n";
    my $fp;    #  = *STDIN;
    print "Process before open $name: ", Dumper($fp), "\n";
    open $fp, '<', $name if $name;
    print "Process  after open $name: ", Dumper($fp), "\n";
    process('b') if $name eq 'a';
    print "Processing file '$name' (fp=$fp)\n";
    print "Contents of $name:\n";
    print while (<$fp>);
    print "Done with $name\n\n\n";
}

这给出了输出:

Starting process with a from main 
Process before open a: $VAR1 = undef;

Process  after open a: $VAR1 = \*{'::$fp'};

Starting process with b from main 
Process before open b: $VAR1 = undef;

Process  after open b: $VAR1 = \*{'::$fp'};

Processing file 'b' (fp=GLOB(0x136412c))
Contents of b:
"b file" 
Done with b


Processing file 'a' (fp=GLOB(0x606f54))
Contents of a:
"a file" 
Done with a


Starting process with c from main 
Process before open c: $VAR1 = undef;

Process  after open c: $VAR1 = \*{'::$fp'};

Processing file 'c' (fp=GLOB(0x136412c))
Contents of c:
"c file" 
Done with c

如果你这样做,只需将那一行改回:

 my $fp = *STDIN;

你得到Dumper报告(为了简洁起见,其余的输出被剪断):

Process before open a: $VAR1 = *::STDIN
Process  after open a: $VAR1 = *::STDIN;

然而,显然 正在打开,因为它正在打印文件内容。

如果您启动strace并执行两个过程(因此减少):

#!/usr/bin/env perl

my $fh;
open ( $fh, "<", "fishfile" ) or warn $!;
print <$fh>;

运行此strace myscript。 (注意 - strace是一个特定于Linux的工具 - 其他平台还有其他工具)

(注意 - 我正在使用一个名为fishfile的文件,其内容为fish,因为这样我很确定我可以找到该文字:))

执行两次 - 一旦分配STDIN,您将看到围绕open操作的一些差异。通过diff运行它们,你会看到很多,但有趣的部分是:

没有STDIN作业:

open ( "fishfile", O_RDONLY) = 3
read (3, "fish\n", 8192 )    = 5
write ( 1, "fish\n", 5 )     = 5

使用STDIN作业:

open ( "fishfile", O_RDONLY) = 3
dup2 ( 3, 0 )                = 0 
close ( 3 )                  = 0
read (0, "fish\n", 8192 )    = 5
write ( 1, "fish\n", 5 )     = 5

(注意 - open的返回码是文件描述符号 - 例如3)

那么实际做的是:

  • 打开新文件
  • 在文件描述符零(即STDIN
  • 上复制它
  • 阅读新的STDIN
  • 将其写入文件描述符1STDOUT。 (2STDERR)。

结果 - 因为你是 - 通过这样做 - 用你自己的文件描述符来破坏STDIN,并且因为STDIN是全局范围的(而不是你的$fh是词法的作用域):

  • 您在子流程STDIN中屏蔽了b然后将其读取到EOF,这意味着当a开始阅读它时,那里什么也没有。

但是,如果您在调用open 之后将b移至

sub process {
    my $name = shift;
    print "Starting process with $name from ", scalar caller(), " \n";
    my $fp = *STDIN;

    process('b') if $name eq 'a';
    print "Process before open $name: ", Dumper($fp), "\n";
    open $fp, '<', $name if $name;
    print "Process  after open $name: ", Dumper($fp), "\n";

    print "Processing file '$name' (fp=$fp)\n";
    print "Contents of $name:\n";
    print while (<$fp>);
    print "Done with $name\n\n\n";
}

成功运作。我假设根据您之前的问题,这与处理文件,然后根据内容打开子流程有关。

因此,解决方案将在克隆$name之前测试STDIN是否存在,您将不会遇到问题。

答案 1 :(得分:3)

my $fp = *STDIN;            
open $fp, '<', $name if $name;

相同
my $fp = *STDIN;            
open STDIN, '<', $name if $name;

您正在指示open更改STDIN。如果要创建新句柄,则需要将未定义的标量传递给open

use strict;
use warnings;
qx{echo "a file" > a};
qx{echo "b file" > b};
qx{echo "c file" > c};

process('a');
process('c');
process('');

sub process {
    my $name = shift;
    my $fp;
    if( defined( $name ) and length( $name ) ) {
      open $fp, '<', $name
    }else{
      $fp = *STDIN;
    }
    process('b') if defined($name) and $name eq 'a';

    print "Processing file '$name' (fp=$fp)\n";
    print while(<$fp>);
}

使用echo stdin | perl scriptname进行测试。

警告if length($name)切换到if $name以避免&#34;行为不端&#34;使用名为0的文件。