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

How to open a filehandle with an existing variable in perl?

在我的 Perl 脚本中,我想处理来自 STDIN 或给定文件(如果指定)的行,与 Linux/UNIX 命令行实用程序一样常见。

为此,我的脚本中有以下部分(针对 post 进行了简化):

use strict;
use warnings;

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

本质上,我将 $in 定义为对 STDIN 类型团的引用,因此通常情况下,如果未指定参数,脚本会为 [=] 的每一行执行 print 17=]。到目前为止,还不错。

但是,如果定义了 $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 是否不起作用?

相关文档确实包括以下内容

About filehandles

The first argument to open, labeled FILEHANDLE in this reference, is usually a scalar variable. (Exceptions exist, described in "Other considerations", below.) If the call to open succeeds, then the expression provided as FILEHANDLE will get assigned an open filehandle. That filehandle provides an internal reference to the specified external file, conveniently stored in a Perl variable, and ready for I/O operations such as reading and writing.

仅凭这一点,我不明白为什么我的代码无法运行。

这正是 null filehandle <> 所做的

Input from <> comes either from standard input, or from each file listed on the command line.

所以你只需要

while (<>) { 
    ...
}

(请参阅文档的其余部分)

另一种在某些情况下更安全的选择是使用双菱形支架

while (<<>>) { } 

Using double angle brackets inside of a while causes the open to use the three argument form (with the second argument being <), so all arguments in ARGV are treated as literal filenames (including "-"). (Note that for convenience, if you use <<>> and if @ARGV is empty, it will still read from the standard input.)

(再次,请查看文档的其余部分)


对于问题的第二部分,以及在评论中的讨论之后,值得注意的是 my $in = \*STDIN 创建了 别名 STDIN (不是副本);请参阅 . Then open-ing 具有此类标量(之前已分配对类型团的引用)的文件作为文件句柄仅重定向原始类型团。所以这里一旦我们 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 仍然打印文件。

STDIN 已被 open 重新连接到文件;找回它并不简单。因此,如果实际上要将 STDIN 与词汇相关联,那么 dupe 更好。请参阅文档和链接 post.


至于直接问题 -- 是的,可以通过 open-ing 重新分配文件句柄。

但是 ... or die if ... 语法是错误的,因为不能像那样链接条件。

但是,我无法重现显示的行为,因为您的代码实际上对我有效(在 Linux 上的 5.16 和 5.30 上)。那么我最好的猜测是这样的代码会导致“未定义的行为”,我们会得到不可预测和不一致的行为。

考虑

E1 or E2 if E3;

其中 Es 代表表达式。 (这是为了 open(...) or die($!) if COND;

if E3 应该适用于什么——单独的 E2 还是整个 E1 or E2?没有办法告诉你,然后人们可能会得到的是可怕的“未定义行为”(UB)——它可能真的有效,sometimes/under一些conditions/on一些系统,或者任何事情 都可能发生。

现在,可能还有更多内容: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];

但我建议编写一个漂亮且可读的测试,而不是将其塞进一个语句中(dup-ing STDIN 作为开始)。

您想使用神奇的 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 工具不同的是,这仅从提供的第一个文件中读取。使用第一个解决方案从提供的每个文件中读取。