为什么我可以通过将尖括号的结果分配给输入记录分隔符来 slurp 一个文件?

Why can I slurp a file by assigning the result of angle brackets to the input record separator?

我知道我可以通过设置 the input record separator ($/) to an undefined value 来 slurp 一个文件,比如

open my $fh, '<', $filename or die "Cannot read from $file: $!";
my $contents = do { local $/; <$fh> };

但最近我遇到了一个非常相似但又不同的成语:

open my $fh, '<', $filename or die "Cannot read from $file: $!";
my $contents = do { local $/ = <$fh> };

(注意 local $/ = <$fh> 而不是 local $/; <$fh>)。

这两个 工作 ,并且在 CPAN 上有两个 the variant with the assignment and the one without 的例子(尽管后者并不奇怪,更常见)。

但我的问题是为什么它有效?赋值的变体是什么?

PS:我知道我应该使用例如。 File::Slurper 吃文件,但生活有时就是这样有趣。

这是您不应依赖的未记录优化的结果。


通常LHS = RHS是这样计算的:

  1. 计算 RHS。
  2. 评估 LHS。
  3. 作业已评估。

如您所见,首先评估赋值的右侧[1]。这允许以下工作:

my $x = 123;

{
   my $x = $x * 2;
   say $x;  # 456
}

say $x;  # 123

显然,在您的案例中发生了一些不同的事情——而且没有记录在案。那是因为 LHS = <$fh> 很特别。 readline (<>) 不是从文件中读取然后将结果分配给左侧,而是直接写入分配左侧的结果。[ 2]

  1. 评估 LHS。 (这会备份 $/ 并在您的情况下将其设置为 undef。)
  2. $fh被评价。
  3. readline 被评估,直接写入赋值左侧的结果。

未执行任何分配。

此优化未记录,您不应依赖它。

例如,

local $/ = uc(<$fh>) 将不起作用。


  1. 编译后的代码首先计算右侧:

    $ perl -MO=Concise,-exec -e'$L = $R'
    1  <0> enter
    2  <;> nextstate(main 1 -e:1) v:{
    3  <#> gvsv[*R] s                   <- $R
    4  <#> gvsv[*L] s                   <- $L
    5  <2> sassign vKS/2                <- =
    6  <@> leave[1 ref] vKP/REFC
    -e syntax OK
    

    下面显示了首先评估的右侧:

    $ perl -e'sub f :lvalue { CORE::say $_[0]; $x } f("L") = f("R")'
    R
    L
    
  2. $x = uc(<>)$x 之前计算 uc(<>),然后执行赋值:

    $ perl -MO=Concise,-exec -e'$x = uc(<>)'
    1  <0> enter
    2  <;> nextstate(main 1 -e:1) v:{
    3  <#> gv[*ARGV] s                  \
    4  <1> readline[t3] sK/1             > RHS
    5  <1> uc[t4] sK/1                  /
    6  <#> gvsv[*x] s                   -> LHS
    7  <2> sassign vKS/2
    8  <@> leave[1 ref] vKP/REFC
    -e syntax OK
    

    $x = uc(<>)<> 之前计算 $x,并且它不执行赋值:

    $ perl -MO=Concise,-exec -e'$x = <>'
    1  <0> enter
    2  <;> nextstate(main 1 -e:1) v:{
    3  <#> gvsv[*x] s                   -> LHS
    4  <#> gv[*ARGV] s                  \  RHS
    5  <1> readline[t3] sKS/1           /
    6  <@> leave[1 ref] vKP/REFC
    -e syntax OK
    

    请注意 readline 旁边的(大写)S 之前不存在。这个 "special" 标志告诉 readline 写入 $x.

    添加 local 不会改变任何东西。

    $ perl -MO=Concise,-exec -e'local $x = <>'
    1  <0> enter
    2  <;> nextstate(main 1 -e:1) v:{
    3  <#> gvsv[*x] s/LVINTRO
    4  <#> gv[*ARGV] s
    5  <1> readline[t3] sKS/1
    6  <@> leave[1 ref] vKP/REFC
    -e syntax OK