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;
}

@blah2番目の問題は、 (という名前の配列blah)と$blah(という名前のスカラー)を混合していることですblah。これが「初期化されていない値」エラー$blahの原因です-(スカラー)は初期化されていませんが、使用しています。$nからの-番目の行が必要な場合は@blah、次を使用します。

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

私は死んだ馬を殴り続けたくありませんが、何かをするためのより良い方法に取り組みたいと思います:

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

これにより、現在のディレクトリで拡張子が「.txt」のすべてのファイルのリストが読み込まれます。これは機能し、効果的ですが、かなり遅くなる可能性があります。シェルを呼び出す必要があります。シェルを実行するにはフォークオフする必要がlsありgrep、少しオーバーヘッドが発生します。さらに、lsgrepは単純で一般的なプログラムですが、正確に移植できるわけではありません。確かにこれを行うためのより良い方法があります:

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

シンプルで短く、純粋なPerl、フォーク、移植性のないシェルはなく、文字列を読み取って分割する必要はありません。本当に必要なエントリのみを保存できます。さらに、テストに合格したファイルの条件を変更するのは簡単になります。正規表現が一致するために誤ってファイルを読み取ってtest.txt.gzしまうとしましょう。その行を次のように簡単に変更できます。

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

(私は信じていますが)それを行うことができますが、Perlに最も強力な正規表現ライブラリの1つが組み込まれているgrepのに、なぜの限られた正規表現に落ち着くのでしょうか?grep

于 2009-09-30T18:01:57.327 に答える
1

演算子$files[$i]の内側を中かっこで囲みます<>

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

それ以外の場合、Perl<>はread-from-filehandle演算子ではなくfileglob演算子として解釈します。

于 2009-09-30T18:03:10.787 に答える
1

あなたはすでにいくつかの良い答えを持っています。この問題に取り組むもう 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 に答える