如何在perl中打开带有现有变量的文件句柄?

inkz8wg9  于 2022-11-15  发布在  Perl
关注(0)|答案(2)|浏览(160)

在我的Perl脚本中,我希望处理来自STDIN或给定文件(如果指定)的行,这与Linux/UNIX命令行实用程序一样。
为此,我在我的脚本中有以下部分(为帖子简化):

use strict;
use warnings;

my $in = \*STDIN;
open $in, '<', $ARGV[0] or die if (defined $ARGV[0]);
print while (<$in>);

实际上,我将$in定义为对STDIN typeglob的引用,因此通常情况下,如果没有指定参数,脚本将对<STDIN>的每一行执行print
然而,如果定义了$ARGV[0],我想从那里读取行。这就是第二个有意义的行所要做的。然而,当使用参数运行时,似乎没有行被处理。
我注意到,在条件调用open之后,$in并没有改变,即使我希望它改变;

my $in = \*STDIN;
print $in, "\n";

open $in, '<', $ARGV[0] or die if (defined $ARGV[0]);
print $in, "\n";

收益率

GLOB(0xaa08b2f4f28)
GLOB(0xaa08b2f4f28)

即使定义了$ARGV[0]。当传递的第一个变量已经引用了文件句柄时,open是否不起作用?
相关文档包括以下内容
关于文件句柄
要打开的第一个参数(在本参考中标记为FILEHANDLE)通常是标量变量。如果对open的调用成功,则作为FILEHANDLE提供的表达式将被分配一个open文件句柄。该文件句柄提供对指定外部文件的内部引用,方便地存储在Perl变量中,并且准备好进行诸如阅读和写的I/O操作。
仅基于这一点,我不明白为什么我的代码不能工作。

fhity93d

fhity93d1#

这正是空文件句柄〈〉的作用
<>的输入来自标准输入,或来自命令行上列出的每个文件。
所以你只需要

while (<>) { 
    ...
}

(see其余的关于它的医生说)
另一种在某些情况下更安全的选择是使用双菱形括号

while (<<>>) { }

在while中使用双尖括号会导致open使用三个参数的形式(第二个参数是<),因此ARGV中的所有参数都被视为文本文件名(包括“-”)。(请注意,为了方便起见,如果使用<<>>,并且@ARGV为空,它仍然会从标准输入中读取。)
(再次,请参阅文档的其余部分)
对于问题的第二部分,以及在评论中的讨论之后,值得注意的是my $in = \*STDIN创建了STDIN的 * 别名 *(不是副本);参见this post。然后打开一个标量为filehandle的文件(之前已经被分配了一个typeglob的引用),仅仅是重定向了原来的typeglob。所以这里,一旦我们open$in文件句柄,那么STDIN就连接到了那个文件。
这很容易检查

perl -wE'
    $in = \*STDIN; 
    say "\$in: $$in";                   #--> *main::STDIN
    print while <$in>;                  # type input, then Ctrl-D
    open $in, "<", $ARGV[0] or die $!; 
    say "\$in is: $$in";                #--> *main::STDIN
    print while <$in>;                  # but prints the file
    seek $in, 0, 0; 
    print while <STDIN>;                # prints the file
' file

在我们输入一些输入后,它会被打印出来,然后按Ctrl-D,在open-ing文件后,文件句柄仍然显示为STDIN,但它确实打印出了这个文件。然后打印STDIN仍然打印出了这个文件。
open已将STDIN重新连接到文件;把它找回来并不简单。所以如果你真的想把STDIN和一个词法关联起来,那么最好复制它。见文档和链接的帖子。
至于直接的问题--是的,可以通过open-ing来重新分配文件句柄。
但是... or die if ...语法是错误的,因为不能像那样链接条件句。
但是,我无法再现所显示的行为,因为您的代码实际上对我有效(在Linux上的5.16和5.30上)。我最好的猜测是,这样的代码会导致“未定义的行为”,我们会得到不可预测和不一致的行为。
考虑

E1 or E2 if E3;

其中,E s代表表达式。(这适用于open(...) or die($!) if COND;
if E3应该应用于什么--单独的E2还是整个E1 or E2?没有办法告诉我们,人们很可能会得到可怕的“未定义行为”(UB)--它可能实际上工作,有时/在某些条件下/在某些系统上,或者 * 任何 * 其他可能发生的事情。
现在,可能还有一点:E2 if E3 * 不能 * 是条件的一部分,因此将其全部解释为E1 or (E2 if E3);是直接非法的语法,因此在我的程序中,该语句可能被解释为

(E1 or E2) if E3;

这很好(并且按预期工作,正如它发生的那样)。然而,原始语句仍然必须是UB,并且在OP的系统上它不工作。
因此,如果您确实需要一个文件句柄,* 至少 * 可以通过添加括号来解决

(open $in, '<', $ARGV[0] or die $!) if defined $ARGV[0];

但是我建议编写一个漂亮的、可读的测试,而不是把它塞进一个语句中(从STDIN开始)。

1dkrff03

1dkrff032#

您希望使用神奇的ARGV文件句柄,它可以完全满足您的需要。
下面是最安全的阅读方式:

while (<<>>) {
   ...
}

你想做的是这样的:

my $in_fh;
if ( @ARGV ) {
  open( $in_fh, "<", $ARGV[0] )
     or die( "Can't open `$ARGV[0]`: $!\n" );
} else {
   $in_fh = \*STDIN;
}

while (<$in_fh>) {
   ...
}

但是,与unix工具不同的是,这只从提供的第一个文件读取。使用第一个解决方案从提供的每个文件读取。

相关问题