函数定义改变了 <> 的外在行为

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 @_.
}