【问题标题】:How do I open an array of files in Perl?如何在 Perl 中打开文件数组?
【发布时间】:2010-12-02 17:58:55
【问题描述】:

在 perl 中,我从一个目录中读取文件,并且我想同时打开它们(但逐行),以便我可以执行一个将所有第 n 行一起使用的功能(例如连接)。

my $text = `ls | grep ".txt"`;
my @temps = split(/\n/,$text);
my @files;
for my $i (0..$#temps) {
  my $file;
  open($file,"<",$temps[$i]);
  push(@files,$file);
}
my $concat;
for my $i (0..$#files) {
  my @blah = <$files[$i]>;
  $concat.=$blah;
}
print $concat;

我只是一堆错误,使用未初始化的值,以及 GLOB(..) 错误。那么我怎样才能完成这项工作呢?

【问题讨论】:

  • Alwaysuse strict; use warnings; 放在 Perl 程序的开头。这样做,直到你确切知道为什么要这样做。
  • “这样做直到你知道你为什么要这样做。”然后继续这样做,除非你有一个非常具体的理由不这样做,并且确切地知道会发生什么。

标签: perl file simultaneous


【解决方案1】:

这是你的问题:

for my $i (0..$#files) {
  my @blah = <$files[$i]>;
  $concat .= $blah;
}

首先,&lt;$files[$i]&gt; 不是有效的文件句柄读取。这是您的 GLOB(...) 错误的来源。请参阅mobrule's answer 了解为什么会这样。所以改成这样:

for my $file (@files) {
  my @blah = <$file>;
  $concat .= $blah;
}

第二个问题,你混合了@blah(一个名为blah的数组)和$blah(一个名为blah的标量)。这是您的“未初始化值”错误的来源 - $blah(标量)尚未初始化,但您正在使用它。如果您想要来自@blah$n-th 行,请使用:

for my $file (@files) {
  my @blah = <$file>;
  $concat .= $blah[$n];
}

我不想一直打死马,但我确实想找到一种更好的方法来做某事:

my $text = `ls | grep ".txt"`;
my @temps = split(/\n/,$text);

这会读入当前目录中具有“.txt”扩展名的所有文件的列表。这很有效,而且很有效,但它可能相当慢 - 我们必须调用 shell,它必须分叉才能运行 lsgrep,这会产生一些开销。此外,lsgrep 是简单而常见的程序,但不是完全可移植的。当然有更好的方法来做到这一点:

my @temps;
opendir(DIRHANDLE, ".");
while(my $file = readdir(DIRHANDLE)) {
  push @temps, $file if $file =~ /\.txt/;
}

简单、简短、纯 Perl,没有分叉,没有不可移植的 shell,而且我们不必读取字符串并 然后 拆分它——我们只能存储我们真正需要的条目需要。另外,修改通过测试的文件的条件变得微不足道。假设我们最终意外读取了文件test.txt.gz,因为我们的正则表达式匹配:我们可以轻松地将该行更改为:

  push @temps, $file if $file =~ /\.txt$/;

我们可以用grep 做到这一点(我相信),但是当Perl 内置了最强大的正则表达式库之一时,为什么还要满足于grep 的有限正则表达式呢?

【讨论】:

    【解决方案2】:

    &lt;&gt; 运算符内使用大括号括住$files[$i]

    my @blah = <{$files[$i]}>
    

    否则 Perl 将 &lt;&gt; 解释为文件 glob 运算符而不是 read-from-filehandle 运算符。

    【讨论】:

    • 我知道&lt;$files[$i]&gt; 不好是有原因的。但这不是该循环中唯一的问题。
    【解决方案3】:

    很多问题。从调用“ls | grep”开始:)

    让我们从一些代码开始:

    首先,让我们获取文件列表:

    my @files = glob( '*.txt' );
    

    但最好测试给定名称是否与文件或目录相关:

    my @files = grep { -f } glob( '*.txt' );
    

    现在,让我们打开这些文件来阅读它们:

    my @fhs = map { open my $fh, '<', $_; $fh } @files;
    

    但是,我们需要一种处理错误的方法——我认为最好的方法是添加:

    use autodie;
    

    在脚本的开头(以及安装 autodie,如果你还没有的话)。或者,您可以:

    use Fatal qw( open );
    

    现在,我们有了它,让我们从所有输入中获取第一行(如您在示例中所示),并将其连接起来:

    my $concatenated = '';
    
    for my $fh ( @fhs ) {
        my $line = <$fh>;
        $concatenated .= $line;
    }
    

    这非常好,可读,但仍然可以缩短,同时保持(在我看来)可读性,以:

    my $concatenated = join '', map { scalar <$_> } @fhs;
    

    效果相同 - $concatenated 包含所有文件的第一行。

    所以,整个程序应该是这样的:

    #!/usr/bin/perl
    use strict;
    use warnings;
    use autodie;
    # use Fatal qw( open ); # uncomment if you don't have autodie
    
    my @files        = grep { -f } glob( '*.txt' );
    my @fhs          = map { open my $fh, '<', $_; $fh } @files;
    my $concatenated = join '', map { scalar <$_> } @fhs;
    

    现在,您可能想要连接的不仅仅是第一行,而是所有的。在这种情况下,您需要这样的代码,而不是 $concatenated = ... 代码:

    my $concatenated = '';
    
    while (my $fh = shift @fhs) {
        my $line = <$fh>;
        if ( defined $line ) {
            push @fhs, $fh;
            $concatenated .= $line;
        } else {
            close $fh;
        }
    }
    

    【讨论】:

    • +1 你的代码比我的好。我很想维护这种代码。尽管为了完整起见,可能会注意到 glob() 被认为是一个有点不安全的函数,并且可能无法普遍工作。我找不到这方面的参考资料(您可以搜索 StackOverflow 并查看是否可以找到有关它的任何信息 - 我从评论中记得它,但现在不知道在哪里查看)。
    • @Chris:嗯.. 没听说过,但有可能。在这种情况下 - opendir, readdir + grep, closedir 应该足够了。
    • 我认为关于glob 的投诉是指该功能的旧版本。 (它曾经使用 C-shell?)不过,这里有一位不喜欢它的 Perl 编码人员,原因是:sial.org/blog/2008/01/many_small_errors.html
    • glob,带有一个固定的字符串参数,在 Perl >= 5.6 上,是相当安全的。该博客文章中唯一令人信服的论点是空格可能会引起麻烦,但已知"*.txt" 不包含空格。 :)
    • @Paul - 你试过阅读其他 cmets 吗?喜欢 - 我 3 小时前的评论:“......在这种情况下 - opendir, readdir + grep, closedir 应该足够了”:)
    【解决方案4】:

    你已经得到了一些很好的答案。解决该问题的另一种方法是创建一个列表列表,其中包含文件中的所有行 (@content)。然后使用List::MoreUtils 中的each_arrayref 函数,它将创建一个迭代器,从所有文件中产生第1 行,然后是第2 行,等等。

    use strict;
    use warnings;
    use List::MoreUtils qw(each_arrayref);
    
    my @content =
        map {
            open(my $fh, '<', $_) or die $!;
            [<$fh>]
        }
        grep {-f}
        glob '*.txt'
    ;
    my $iterator = each_arrayref @content;
    while (my @nth_lines = $iterator->()){
        # Do stuff with @nth_lines;
    }
    

    【讨论】:

    • @Brad - 可能,但有点神秘。
    猜你喜欢
    • 1970-01-01
    • 2012-12-06
    • 2011-01-28
    • 1970-01-01
    • 2011-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-13
    相关资源
    最近更新 更多