【问题标题】:Line Input operator with glob returning old values带有 glob 返回旧值的行输入运算符
【发布时间】:2017-12-04 23:27:59
【问题描述】:

以下摘录代码在 perl 5.16.3 和更早的版本上运行时有一个奇怪的行为,其中在行输入运算符中对 glob 的后续调用导致 glob 继续返回以前的值,而不是重新运行 glob .

#!/usr/bin/env perl

use strict;
use warnings;

my @dirs = ("/tmp/foo", "/tmp/bar");

foreach my $dir (@dirs) {    
    my $count = 0;
    my $glob = "*";
    print "Processing $glob in $dir\n";
    while (<$dir/$glob>) {
        print "Processing file $_\n";
        $count++;
        last if $count > 0;
    }
}

如果您将两个文件放在 /tmp/foo 中,并将一个或多个文件放在 /tmp/bar 中,然后运行代码,我会得到以下输出:

在 /tmp/foo 中处理 *

处理文件/tmp/foo/foo.1

在 /tmp/bar 中处理 *

处理文件/tmp/foo/foo.2

我认为当whilelast 之后终止时,在第二次迭代中对while 的新调用将重新运行 glob 并给我列出的文件 /tmp/bar ,但我得到了 /tmp/foo 中内容的延续。

这几乎就像角度运算符 glob 的行为类似于预编译模式。我的假设是角度运算符正在符号表中创建一个文件句柄,该文件句柄仍处于打开状态并在后台被重用,并且它的范围为包含foreach,或者可能是整个子例程。

【问题讨论】:

  • 在 cmets 进入之前,是的,如果我删除角度运算符 glob 并通过显式调用 glob() 和 foreach 来运行它,我将得到我期望的行为。我想知道为什么会这样,而不是如何解决它。
  • Re "在行输入运算符中对 glob 的后续调用导致 glob 继续返回以前的值",显然应该如此。如果&lt;$dir/$glob&gt; 总是返回第一个文件,那就没用了。

标签: perl glob


【解决方案1】:

来自I/O Operators in perlop (我的重点)

一个(文件)glob 仅在它开始一个 新名单。 在重新开始之前必须读取所有值。 在列表中 上下文,这并不重要,因为您会自动获取所有内容 反正。但是,在标量上下文中,运算符返回下一个值 每次调用它,或者当列表用完时undef

由于&lt;&gt; 在此处以标量上下文调用,并且您在第一次迭代后使用last 退出循环,因此下次您输入它时,它会继续从原始列表中读取。


在 cmets 中澄清了这个任务背后的实际需求:只处理目录中的一些文件并且从不返回所有文件名,因为可能有很多。

因此,从glob 分配到一个列表并使用它,或者更好的是使用for 而不是while,正如ysth 评论的那样,在这里没有帮助,因为它返回一个巨大的列表。

我还没有找到一种方法让glob&lt;&gt; 使用文件名模式)在列表生成后删除并重建列表,而无需先结束。 显然,运算符的每个实例都有自己的列表。因此,在 while 循环中使用另一个 &lt;&gt; 并希望以任何方式甚至使用相同的模式重置它,不会影响在 while (&lt;$glob&gt;) 中迭代的列表。

请注意,使用diewhileeval 中)打破循环也无济于事;下次我们来到while 时,将继续使用相同的列表。将其包裹在一个闭包中

sub iter_glob { my $dir = shift; return sub { scalar <"$dir/*"> } }

for my $d (@dirs) {
    my $iter = iter_glob($d);
    while (my $f = $iter->()) {
        # ...
    }
}

遭遇同样的命运;原始列表继续使用。

那么解决方案是改用readdir

【讨论】:

  • 显式 glob 根本没有帮助;最简单的解决方法是使用 for 而不是 while
  • 好的,所以 line 运算符确实在创建一些全局引用,这些引用在下一次 foreach 迭代中被重用。我还尝试将此代码放在子例程中以强制范围退出,并且 glob 在子例程的重复调用中保持状态。我想教训就是不要这样做。
  • @ysth 事实上,正如我上面指出的,这就是解决这个问题的方法。话虽如此,代码最初没有这样做的原因是由于 glob 可能返回的潜在大量文件。当无论如何只处理有限的最大数量时,开发人员试图提高效率并且不消耗大量内存。但是,看起来这种策略行不通。
  • @ysth 我说glob是在列表上下文中,它每次都会重新构建它(然后用foreach迭代)。但是正如您所说,foreach 是一种更简单的方法(除非它具有不返回完整列表的“优化”,就像它对范围所做的那样)。
  • @CDahn 我尝试使用die 提前退出while(在eval 中使用while 循环),希望它可以“重置”它,但它仍然返回打印第一个目录的内容...
猜你喜欢
  • 2014-07-01
  • 2020-10-02
  • 1970-01-01
  • 2021-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-04
相关资源
最近更新 更多