3

在 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(..) 错误。那么我怎样才能使这项工作呢?

4

4 回答 4

15

很多问题。从调用“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;
    }
}
于 2009-09-30T18:29:39.127 回答
8

这是你的问题:

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

首先,<$files[$i]>不是有效的文件句柄读取。这是您的 GLOB(...) 错误的来源。请参阅mobrule 的答案,了解为什么会这样。所以把它改成这样:

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

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

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

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

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

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

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$/;

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

于 2009-09-30T18:01:57.327 回答
1

在运算符$files[$i]内部使用大括号<>

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

否则 Perl 解释<>为文件 glob 操作符而不是 read-from-filehandle 操作符。

于 2009-09-30T18:03:10.787 回答
1

你已经得到了一些很好的答案。解决该问题的另一种方法是创建一个列表列表,其中包含文件中的所有行 ( @content)。然后使用List::MoreUtilseach_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;
}
于 2009-09-30T18:54:03.977 回答