在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(..)错误。那么我怎样才能做到这一点呢?
答案 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
,这会产生一些开销。此外,grep
和ls
是简单而常见的程序,但不是完全可移植的。当然有更好的方法来做到这一点:
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;
}