如何在Perl中打开文件数组?

时间:2009-09-30 17:52:28

标签: perl file simultaneous

在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 个答案:

答案 0 :(得分: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;
    }
}

答案 1 :(得分:8)

这是你的问题:

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

首先,<$files[$i]>不是有效的文件句柄读取。这是您的GLOB(...)错误的来源。请参阅mobrule's answer了解为何会出现这种情况。所以改成它:

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

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

@blah

我不想继续打死马,但我确实希望找到更好的办法来做点什么:

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

这将读入当前目录中所有文件的列表,其中包含“.txt”扩展名。这是有效的,并且有效,但它可能相当慢 - 我们必须调用shell,它必须分叉运行my $text = `ls | grep ".txt"`; my @temps = split(/\n/,$text); ls,这会产生一些开销。此外,grepls是简单而常见的程序,但不是完全可移植的。当然有更好的方法来做到这一点:

grep

简单,简短,纯粹的Perl,没有分叉,没有非便携式shell,我们不必读取字符串而然后拆分它 - 我们只能存储我们真正的条目需要。另外,修改通过测试的文件的条件变得微不足道。假设我们最终意外地读取文件my @temps; opendir(DIRHANDLE, "."); while(my $file = readdir(DIRHANDLE)) { push @temps, $file if $file =~ /\.txt/; } ,因为我们的正则表达式匹配:我们可以轻松地将该行更改为:

test.txt.gz

我们可以用 push @temps, $file if $file =~ /\.txt$/; (我相信)做到这一点,但是当Perl拥有内置的最强大的正则表达式库之一时,为什么要解决grep有限的正则表达式呢?

答案 2 :(得分:1)

$files[$i]运算符

中的<>周围使用大括号
my @blah = <{$files[$i]}>

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

答案 3 :(得分:1)

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

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;
}
相关问题