重要提示:该问题的动机不是解决问题,而是了解Perl的行为。
请考虑以下玩具脚本:
#!/usr/bin/env perl
use strict;
sub main {
@ARGV >= 2 or die "$0: not enough arguments\n";
my $arg_a = shift @ARGV;
my $arg_b = shift @ARGV;
while ( <> ) {
print "+++ $_";
}
}
main();
__END__
此脚本带有2个或更多参数(不使用)。它所做的只是回显(使用+++
前缀)标准输入,或者将许多文件的内容指定为第三,第四等参数。
到目前为止,代码的行为符合我的预期。
现在考虑这个经过稍微修改的版本:
#!/usr/bin/env perl
use strict;
sub slurp {
local $/ = undef;
local @ARGV = @_;
return <>;
}
sub main {
@ARGV >= 2 or die "$0: not enough arguments\n";
my $arg_a = shift @ARGV;
my $arg_b = shift @ARGV;
my $content = slurp( $arg_a );
while ( <> ) {
print "+++ $_";
}
}
main();
__END__
此脚本版本不会忽略其第一个参数;而是将其解释为文件的路径,并将其内容读入变量$content
(随后将其忽略)。除此之外,该脚本的行为应与以前完全相同。
不幸的是,此版本的脚本不再回显其stdin(尽管它仍然回显其3rd,4th等参数的内容)。
我知道问题与slurp
函数的实现方式有关,因为如果我将此实现更改为
sub slurp {
local $/ = undef;
open my $input, $_[ 0 ] or die "$!";
return <$input>;
}
...然后,脚本再次响应其标准输入(如果可用)。
我想了解为什么slurp
的第一个版本导致脚本按预期停止工作。
答案 0 :(得分:2)
要使<>
与STDIN
一起使用,必须在@ARGV
为空时调用它。如果运行@ARGV
时<>
中有文件名,则在读取文件时会将它们从那里删除,然后您需要再次调用<>
以便等待STDIN
。
perl -wE'if (@ARGV) { print while <> }; print while <>' file
第二个print while <>
等待STDIN
(不打印file
并退出程序)。
如果从@ARGV
读取所有文件,并且一旦控件返回到主要的<>
调用之后,则子程序原则上会发生这种情况,然后您将等待{{ 1}}。
但是,您的子将STDIN
本地化(好的做法!),因此,一旦退出全局@ARGV
,它仍然具有开始时的功能。 †然后,主体中的@ARGV
再次读取这些文件,在最后一个文件的末尾获得一个应有的while
,然后退出。
一种查看方式:在调用读取输入的子程序之后,在主程序undef
之前,从@ARGV
中删除所有内容。然后,while
将再次等待while
,而与子无关。喜欢
STDIN
(需要注意的一个细节是,您的示例似乎使用了两个文件,而子文件处理了一个文件,因此即使子文件使用的是全局perl -wE'
sub ri { local @ARGV = @_; return <> };
print for ri(@ARGV);
say"argv: @ARGV";
@ARGV=();
print while <>
' file
(不是@ARGV
化的文件),从local
中删除一个文件,仍然有一个文件占据了@ARGV
的主体。因此您仍然不会得到while
。)
查看全部内容的另一种方法:在末尾添加另一个STDIN
; 那个一个人会在print while <>
上等待。
这一切都在I/O Operators (perlop)中进行了描述,尽管需要仔细阅读。
†在STDIN
上,local $GLOBAL_VAR;
的值被复制掉,并在退出该范围时恢复。因此,local在其范围内保护全局变量不受更改。
答案 1 :(得分:2)
在考虑再次使用STDIN之前,需要耗尽迭代器(通过调用它直到返回undef
)。
sub slurp {
local $/ = undef;
local @ARGV = @_;
my $rv = <>; # Read file specified by $_[0].
1 while <>; # Exhaust the iterator.
return $rv;
}
或
sub slurp {
local $/ = undef;
local @ARGV = @_;
my $rv = "";
while (my $file = <>) {
$rv .= $file;
}
return $rv; # Concatenation of all files specified by @_.
}