0

私は2つのファイルを持っています。

  1. input.txt と呼ばれる難読化されたファイル
  2. キーと値のペアで構成される mapping.txt という 2 番目のファイル。

input.txt の mapping.txt からキーが出現するたびに見つけて、キーに対応する値に置き換えたいと思います。

一致が成功するたびに、input.txt の行の内容を上書きしたいことに注意してください。

私は次のコードを書きました:

#! /usr/bin/perl

use strict;
use warnings;

(my $mapping,my $input)=@ARGV;

open(MAPPING,'<',$mapping) || die("couldn't read from the file, $mapping with error: $!\n");

while(<MAPPING>)
{
    chomp $_;
    my $line=$_;
    (my $key,my $value)=split("=",$line);
    open(INPUT,'+<',$input);
    while(<INPUT>)
    {
        chomp $_;
        if(index($_,$key)!=-1)
        {
            $_=~s/\Q$key/$value/g;
            # move pointer to beginning of line
           print INPUT $_."\n";
        }
    }
    close INPUT;
}
close MAPPING;

コードの簡単な概要:

  1. mapping.txt ファイルを読み取りモードで開きます。
  2. 各行はキーと値のペアであるため、キーと値に分割されます。
  3. input.txt ファイルを上書きモードで開きます。
  4. キーが現在の行にあるかどうかを確認します。
  5. キーが見つかった場合は、キー内のメタ文字を無視して値に置き換えます (\Q を前に付けます)。
  6. この時点では、前のステートメントが行全体をスキャンしてキーを見つけて置き換えるため、ファイル ポインターは行の末尾になります。
  7. ファイル ポインタを行頭に移動できれば、次のように上書きできます。

    出力 INPUT $_,"\n"

  8. シーク機能を調べてみましたが、この目的で使用する方法がわかりませんでした。

これが完了すると、コードはファイルを閉じます。mapping.txt から次のキーと値のペアを選択し、最初から入力ファイルをスキャンして一致を探し、それらを置き換えます。

最も重要な点は、内側の while ループが毎回、内側の while ループの前の繰り返しで変更された input.txt で動作することです。このようにして、成功した検索および置換操作はすべて、input.txt ファイルに保存され続けます。

どうすればいいですか?

ありがとう。

4

2 に答える 2

3

まず、 の 3 パラメータ形式であるレキシカル ファイル ハンドルを使用しopen、常にステータスをチェックして、openが成功したことを確認する必要があります (入力ファイルではなくマッピング ファイルで行うように)。

置換データが置換するデータとまったく同じサイズでprintない限り、ファイルの一部を更新できないため、使用する前に行の先頭に巻き戻すという提案する解決策は機能しません。これは通常、あなたの状況には当てはまりません。

これにはいくつかの解決策があります。最初の最も簡単な方法は、ループを逆にして、マッピング ファイルの読み取りループを入力ファイルの読み取りループ内に配置することです。コードは次のようになります。

use strict;
use warnings;

my ($mapping, $input) = @ARGV;

open my $infh, '<', $input or die "Unable to open '$input': $!";

while (my $line = <$input>) {

  open my $mapfh, '<', $mapping or die "Unable to open '$mapping': $!";

  while (<$mapfh>) {
    chomp;
    my ($key, $value) = split /=/;
    $line =~ s/\Q$key/$value/g;
  }
  print $line;
}

ただし、出力は STDOUT に送信されるため、出力をファイルに保存して適切に名前を変更する必要があります。

ここでの代替手段は-I、ファイルの名前を自動的に変更し、必要に応じてバックアップを保存するコマンドライン オプションを使用することです。ベア-Iを使用すると、古いファイルを削除して新しい出力の名前を変更することでファイルをその場で変更しますが、パラメーターに次のような値を指定すると、削除する代わりに-I.bak追加することで古いファイルの名前を変更します。.bakこの-Iオプションは、空の演算子を使用して ARGV から読み取ったファイルにのみ適用され<>、組み込み変数$^Iを値 (または空の文字列'') に設定すると、同じ効果があります。コードは次のようになります。

use strict;
use warnings;

my $mapping = shift @ARGV;
$^I = '.bak';

while (my $line = <>) {

  open my $mapfh, '<', $mapping or die "Unable to open '$mapping': $!";

  while (<$mapfh>) {
    chomp;
    my ($key, $value) = split /=/;
    $line =~ s/\Q$key/$value/g;
  }
  print $line;
}

Tie::Fileこれは、Perl 配列をファイルの内容にマップし、配列のすべての変更を元のファイルに反映します。これは例です:

use strict;
use warnings;

use Tie::File;

my ($mapping, $input) = @ARGV;
tie my @input, 'Tie::File', $input or die "Unable to open '$input': $!";

for my $line (@input) {

  open my $mapfh, '<', $mapping or die "Unable to open '$mapping': $!";

  while (<$mapfh>) {
    chomp;
    my ($key, $value) = split /=/;
    $line =~ s/\Q$key/$value/g;
  }
}

最後に、入力行ごとにマッピング ファイルを開いて読み取るのは非常に非効率的です。その内容から正規表現を作成し、それをプログラム全体で使用することをお勧めします。このバージョンでは、最初%mappingにマッピング ファイルからハッシュを作成し、次にquotemeta各ハッシュ キーに適用して正規表現のメタ文字をエスケープし、正規表現代替演算子でそれらを結合して正規表現を作成し|ます。キーは長さの降順でソートされるため、最も長い一致が検出され、短いものよりも優先的に置き換えられます。

use strict;
use warnings;

use Tie::File;

my ($mapping, $input) = @ARGV;

open my $mapfh, '<', $mapping or die "Unable to open '$mapping': $!";
my %mapping = map { chomp; /\S/ ? split /=/ : () } <$mapfh>;
my $regex = join '|', map quotemeta, sort { length $b <=> length $b } keys %mapping;

tie my @input, 'Tie::File', $input or die "Unable to open '$input': $!";

for my $line (@input) {
  $line =~ s/($regex)/$mapping{$1}/g;
}
于 2012-10-08T10:24:34.757 に答える
0

ファイル ポインタを行頭に移動できれば、次のように上書きできます。

print INPUT $_,"\n"

あなたの前提は間違っています: バイト シーケンス00 01 02とルールを仮定すると01 = A1 A2、結果のバイト シーケンスは00 A1 A2and notになり00 A1 A2 02ます。これを回避する方法は次のとおりです。

  • モジュールを使用しTie::Fileます。
  • パスが完成したら、別のファイルに書き込み、2 番目のファイルの名前を元のファイルに変更します。これはおそらく最も効率的でスケーラブルです。

seeking は良い考えではありません: 固定長の置換に制約され、文字ではなくバイトseekを操作することになります。tell本当にインプレース編集を使用する必要がある場合は、次のループを使用できます。

my $beginning_of_line = tell $fh;
while (<$fh>) {
  # do processing
  seek $fh, $beginning_of_line, 0;
  # do update
} continue {$beginning_of_line = tell $fh}

また、入力ファイルに対して複数のパスを作成します。トークン シーケンスa b cとルールb = d eおよびを仮定すると、ルールの順序に応じてd = fシーケンスa f e cまたはが生成されます。これはあなたが望むものではないかもしれません。 また、ルールと入力のあいまいさを考慮してください。これは生成しますか?a d e c
a = ca b = da bc bd

于 2012-10-08T10:34:16.283 に答える