1

更新2:解決しました。下記参照。

私は、大きなtxtファイルを古いDOSベースのライブラリプログラムからより使いやすい形式に変換しているところです。私はPerlで始めたばかりで、次のようなスクリプトをまとめることができました。

BEGIN {undef $/; };
open $in,  '<',  "orig.txt"      or die "Can't read old file: $!"; 
open $out, '>',  "mod.txt"  or die "Can't write new file: $!";
while( <$in> )  
{
$C=s/foo/bar/gm;
print "$C matches replaced.\n"
etc...
print $out $_;
}
close $out;

非常に高速ですが、しばらくすると常に「メモリ不足」が発生します-RAM /スワップスペースが不足しているためエラーが発生します(2GBのRAMと1.5GBのスワップファイルを搭載したWin XPを使用しています)。大きなファイルを処理する方法を少し調べた後、File::Mapこの問題を回避するための良い方法のように思えました。しかし、私はそれを実装するのに苦労しています。これは私が今持っているものです:

#!perl -w
use strict; 
use warnings;
use File::Map qw(map_file);

my $out = 'output.txt';
map_file my $map, 'input.txt', '<';
$map =~ s/foo/bar/gm;

print $out $map;

ただし、次のエラーが発生します。Modification of a read-only value attempted at gott.pl line 8.

また、File::Mapヘルプページで、Unix以外のシステムで使用する必要があることを読みましたbinmode。それ、どうやったら出来るの?

基本的に、私がやりたいのは、File :: Mapを介してファイルを「ロード」してから、次のようなコードを実行することです。

$C=s/foo/bar/gm;
print "$C matches found and replaced.\n"

$C=s/goo/far/gm;
print "$C matches found and replaced.\n"
while(m/complex_condition/gm)
{ 
$C=s/complex/regex/gm;
$run_counter++;
}
print "$C matches replaced. Script looped $run_counter times.\n";
etc...

あまりにも明白なことを見落としていないことを願っていますが、File::Mapヘルプページに示されている例は、マップされたファイルから読み取る方法を示しているだけですよね?

編集:

メモリ不足のために現在達成できないことをよりよく説明するために、例を示します。

http://pastebin.com/6Ehnx6xAには、エクスポートされたライブラリレコード(txt形式)の1つのサンプルがあります。46行目から始まる部分に興味があり+Deskriptoren:ます。これらは、ツリー階層で編成された主題分類子です。

私が欲しいのは、親ノードの完全なチェーンで各分類子を拡張することですが、問題の子ノードの前後に親ノードがまだ存在しない場合に限ります。これは回転することを意味します

+Deskriptoren
-foo
-Cultural Revolution
-bar

の中へ

+Deskriptoren
-foo
-History
-Modern History
-PRC
-Cultural Revolution
-bar

現在使用されている正規表現は、重複の重複を避けるためにLookbehindとLookaheadを使用するため、以下よりも少し複雑ですs/foo/bar/g;

s/(?<=\+Deskriptoren:\n)((?:-(?!\QParent-Node\E).+\n)*)-(Child-Node_1|Child-Node_2|...|Child-Node_11)\n((?:-(?!Parent-Node).+\n)*)/${1}-Parent-Node\n-${2}\n${3}/g;

しかし、それは機能します!Perlがメモリを使い果たすまで...:/

したがって、本質的には、大きなファイル(80MB)を数行にわたって操作する方法が必要です。処理時間は問題ではありません。これが私がFile::Mapを考えた理由です。別のオプションとして、リンクされたperlスクリプトを使用してファイルをいくつかのステップで処理し、相互に呼び出して終了することもできますが、できるだけ1か所に保管したいと思います。

更新2:

以下のSchwelmのコードでなんとか動作させることができました。私のスクリプトは、2つのネストされたサブルーチンを呼び出す次のサブルーチンを呼び出します。サンプルコードは次の場所にあります:http://pastebin.com/SQd2f8ZZ

私が仕事に就けなかったことにまだ完全には満足していませんFile::Map。まあ...とにかくラインアプローチの方が効率的だと思います。

みんな、ありがとう!

4

3 に答える 3

7

(入力レコード区切り文字)をundefinedに設定$/すると、ファイルが「丸呑み」され、ファイルのコンテンツ全体が一度に読み取られます(これについては、たとえばperlvarで説明されています)。したがって、メモリ不足の問題。

代わりに、次のことができる場合は、一度に1行ずつ処理してください。

while (my $line = <$in>){
    # Do stuff.
}

ファイルが十分に小さく、ファイルを丸呑みする状況では、whileループは必要ありません。最初の読み取りですべてが取得されます。

{
    local $/ = undef;
    my $file_content = <>;
    # Do stuff with the complete file.
}

アップデート

あなたの大規模な正規表現を見た後、私はあなたにあなたの戦略を再考することを勧めます。これを構文解析の問題として扱います。ファイルを一度に1行ずつ処理し、必要に応じてパーサーの状態に関する情報を保存します。このアプローチにより、簡単で理解しやすい(テスト可能な)手順を使用して情報を操作できます。

あなたの現在の戦略-それを大規模な正規表現戦略で丸呑みと強打と呼ぶかもしれません-は理解と維持が難しく(3か月以内にあなたの正規表現はあなたにすぐに意味がありますか?)、テストとデバッグが難しく、調整が困難ですデータの最初の理解から予期しない逸脱を発見した場合。さらに、ご存知のように、この戦略はメモリの制限に対して脆弱です(ファイルを丸呑みする必要があるため)。

StackOverflowには、意味のある単位が複数行にまたがる場合にテキストを解析する方法を示す多くの質問があります。別の質問者に同様のアドバイスを提供したこの質問も参照してください。

于 2011-06-11T03:07:39.230 に答える
3

いくつかの単純な解析により、ファイルを管理可能なチャンクに分割できます。アルゴリズムは次のとおりです。

1. Read until you see `+Deskriptoren:`
2. Read everything after that until the next `+Foo:` line
3. Munge that bit.
4. Goto 1.

コードのスケッチは次のとおりです。

use strict;
use warnings;
use autodie;

open my $in,  $input_file;
open my $out, $output_file;

while(my $line = <$in>) {
    # Print out everything you don't modify
    # this includes the +Deskriptoren line.
    print $out $line;

    # When the start of a description block is seen, slurp in up to
    # the next section.
    if( $line =~ m{^ \Q Deskriptoren: }x ) {
        my($section, $next_line) = _read_to_next_section($in);

        # Print the modified description
        print $out _munge_description($section);

        # And the following header line.
        print $out $next_line;
    }
}

sub _read_to_next_section {
    my $in = shift;

    my $section = '';
    my $line;
    while( $line = <$in> ) {
        last if $line =~ /^ \+ /x;
        $section .= $line;
    }

    # When reading the last section, there might not be a next line
    # resulting in $line begin undefined.
    $line = '' if !defined $line;
    return($section, $line);
}

# Note, the +Deskriptoren line is not on $description
sub _munge_description {
    my $description = shift;

    ...whatever you want to do to the description...

    return $description;
}

私はそれをテストしていませんが、そのような何かがあなたをするはずです。ファイル全体を文字列(File :: Mapなど)として処理するよりも、1つの正規表現ですべてのベースをカバーしようとするのではなく、各セクションを個別に処理できるという利点があります。また、上記の単純な構文解析を台無しにする可能性があり、大規模な正規表現を適応させるのに非常に苦労するコメントや文字列などを処理するための、より洗練されたパーサーを開発できます。

于 2011-06-12T03:54:33.760 に答える
1

<読み取り専用のモードを使用しています。コンテンツを変更する場合は、読み取り/書き込みアクセスが必要なので、を使用する必要があります+<

Windowsを使用していて、バイナリモードが必要な場合は、ファイルを個別に開き、ファイルハンドルにバイナリモードを設定してから、そのハンドルからマップする必要があります。

また、入力ファイルと出力ファイルがあることに気づきました。File :: Mapを使用する場合は、ファイルをインプレースで変更します...つまり、ファイルを開いて読み取り、別のファイルの内容を変更することはできません。ファイルをコピーしてから、コピーを変更する必要があります。私は以下でそうしました。

use strict;
use warnings;

use File::Map qw(map_file);
use File::Copy;

copy("input.txt", "output.txt") or die "Cannot copy input.txt to output.txt: $!\n";

open my $fh, '+<', "output.txt"
    or die "Cannot open output.txt in r/w mode: $!\n";

binmode($fh);

map_handle my $contents, $fh, '+<';

my $n_changes = ( $contents =~ s/from/to/gm );

unmap($contents);
close($fh);

のドキュメントFile::Mapは、エラーがどのように通知されるかについてはあまりよくありませんが、ソースからは、$contents未定義であることが適切な推測であるように見えます。

于 2011-06-11T19:18:56.903 に答える