函数定义改变了 <> 的外在行为
Function definition changes the outward behavior of <>
重要提示:这个问题的动机不是为了解决问题,而是为了理解 Perl 的行为。
考虑以下玩具脚本:
#!/usr/bin/env perl
use strict;
sub main {
@ARGV >= 2 or die "[=10=]: 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 "[=11=]: not enough arguments\n";
my $arg_a = shift @ARGV;
my $arg_b = shift @ARGV;
my $content = slurp( $arg_a );
while ( <> ) {
print "+++ $_";
}
}
main();
__END__
这个版本的脚本不会忽略它的第一个参数;相反,它将解释为文件的路径,并将其内容读入变量 $content
(随后它会忽略)。除此之外,脚本的行为应该和以前完全一样。
不幸的是,这个版本的脚本不再回显其标准输入(尽管它仍然回显其第 3 个、第 4 个等参数的内容)。
我知道问题与 slurp
函数的实现方式有关,因为如果我将此实现更改为
sub slurp {
local $/ = undef;
open my $input, $_[ 0 ] or die "$!";
return <$input>;
}
...然后脚本再次回显其标准输入(如果可用)。
我想了解为什么 slurp
的第一个版本导致脚本按预期停止工作。
要使 <>
与 STDIN
一起工作,必须在 @ARGV
为空时调用它。如果当 <>
为 运行 时 @ARGV
中有文件名,它们会在读取文件时从那里删除,然后您 需要调用 <>
再次为了等待STDIN
.
perl -wE'if (@ARGV) { print while <> }; print while <>' file
它是等待 STDIN
的第二个 print while <>
(如果没有它,将打印 file
并退出程序)。
这原则上可能会发生在你的 sub 上,如果它要从 @ARGV
读取所有文件并且一旦控制返回到 main 中的 <>
调用,然后将等待STDIN
。
但是,您的子 本地化 @ARGV
(好习惯!),所以一旦它退出全局 @ARGV
仍然具有它在beginning.† 然后 while
主要读取那些文件(再次),得到那个 undef
它在最后一个文件的末尾到期,并且出口。
查看此内容的一种方法:在调用读取输入的子程序之后和主程序中的 while
之前,从 @ARGV
中删除所有内容。然后 while
将再次等待 STDIN
,而不管 sub。喜欢
perl -wE'
sub ri { local @ARGV = @_; return <> };
print for ri(@ARGV);
say"argv: @ARGV";
@ARGV=();
print while <>
' file
(需要注意的一个细节是,您的示例似乎需要两个文件,而子程序处理一个文件,因此即使子程序使用全局 @ARGV
(不是 local
-ized ) 并从 @ARGV
中删除一个文件,仍然有一个文件留在那里占据主文件中的 while
。所以你仍然不会得到 STDIN
。)
查看所有内容的另一种方式:在末尾添加另一个 print while <>
; 一个人会在 STDIN
上等待。
I/O Operators (perlop) 中对此进行了全部描述,但需要仔细阅读。
† 在 local $GLOBAL_VAR;
上,$GLOBAL_VAR
的值被复制,并在退出该作用域时恢复。因此 local 保护全局变量在其范围内不受更改。
在考虑再次使用 STDIN 之前,您需要耗尽迭代器(通过调用它直到 returns 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 @_.
}
重要提示:这个问题的动机不是为了解决问题,而是为了理解 Perl 的行为。
考虑以下玩具脚本:
#!/usr/bin/env perl
use strict;
sub main {
@ARGV >= 2 or die "[=10=]: 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 "[=11=]: not enough arguments\n";
my $arg_a = shift @ARGV;
my $arg_b = shift @ARGV;
my $content = slurp( $arg_a );
while ( <> ) {
print "+++ $_";
}
}
main();
__END__
这个版本的脚本不会忽略它的第一个参数;相反,它将解释为文件的路径,并将其内容读入变量 $content
(随后它会忽略)。除此之外,脚本的行为应该和以前完全一样。
不幸的是,这个版本的脚本不再回显其标准输入(尽管它仍然回显其第 3 个、第 4 个等参数的内容)。
我知道问题与 slurp
函数的实现方式有关,因为如果我将此实现更改为
sub slurp {
local $/ = undef;
open my $input, $_[ 0 ] or die "$!";
return <$input>;
}
...然后脚本再次回显其标准输入(如果可用)。
我想了解为什么 slurp
的第一个版本导致脚本按预期停止工作。
要使 <>
与 STDIN
一起工作,必须在 @ARGV
为空时调用它。如果当 <>
为 运行 时 @ARGV
中有文件名,它们会在读取文件时从那里删除,然后您 需要调用 <>
再次为了等待STDIN
.
perl -wE'if (@ARGV) { print while <> }; print while <>' file
它是等待 STDIN
的第二个 print while <>
(如果没有它,将打印 file
并退出程序)。
这原则上可能会发生在你的 sub 上,如果它要从 @ARGV
读取所有文件并且一旦控制返回到 main 中的 <>
调用,然后将等待STDIN
。
但是,您的子 本地化 @ARGV
(好习惯!),所以一旦它退出全局 @ARGV
仍然具有它在beginning.† 然后 while
主要读取那些文件(再次),得到那个 undef
它在最后一个文件的末尾到期,并且出口。
查看此内容的一种方法:在调用读取输入的子程序之后和主程序中的 while
之前,从 @ARGV
中删除所有内容。然后 while
将再次等待 STDIN
,而不管 sub。喜欢
perl -wE'
sub ri { local @ARGV = @_; return <> };
print for ri(@ARGV);
say"argv: @ARGV";
@ARGV=();
print while <>
' file
(需要注意的一个细节是,您的示例似乎需要两个文件,而子程序处理一个文件,因此即使子程序使用全局 @ARGV
(不是 local
-ized ) 并从 @ARGV
中删除一个文件,仍然有一个文件留在那里占据主文件中的 while
。所以你仍然不会得到 STDIN
。)
查看所有内容的另一种方式:在末尾添加另一个 print while <>
; 一个人会在 STDIN
上等待。
I/O Operators (perlop) 中对此进行了全部描述,但需要仔细阅读。
† 在 local $GLOBAL_VAR;
上,$GLOBAL_VAR
的值被复制,并在退出该作用域时恢复。因此 local 保护全局变量在其范围内不受更改。
在考虑再次使用 STDIN 之前,您需要耗尽迭代器(通过调用它直到 returns 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 @_.
}