9

30Mb 以上の非常に大きなテキスト ファイルで 600 の異なる文字列を置き換えようとしています。現在、これを行うスクリプトを作成しています。この質問に続いて:

脚本:

$string = gc $filePath 
$string | % {
    $_ -replace 'something0','somethingelse0' `
       -replace 'something1','somethingelse1' `
       -replace 'something2','somethingelse2' `
       -replace 'something3','somethingelse3' `
       -replace 'something4','somethingelse4' `
       -replace 'something5','somethingelse5' `
       ...
       (600 More Lines...)
       ...
}
$string | ac "C:\log.txt"

しかし、これは各行を 600 回チェックし、テキスト ファイルには 150,000 行をはるかに超える行があるため、多くの処理時間がかかることを意味します。

これを行うより効率的な代替手段はありますか?

4

4 に答える 4

6

Adi Inbar's answerのハッシュ手法と、別の最近の質問に対する Keith Hill の回答の一致評価器を組み合わせて、PowerShell で置換を実行する方法を次に示します。

# Build hashtable of search and replace values.
$replacements = @{
  'something0' = 'somethingelse0'
  'something1' = 'somethingelse1'
  'something2' = 'somethingelse2'
  'something3' = 'somethingelse3'
  'something4' = 'somethingelse4'
  'something5' = 'somethingelse5'
  'X:\Group_14\DACU' = '\\DACU$'
  '.*[^xyz]' = 'oO{xyz}'
  'moresomethings' = 'moresomethingelses'
}

# Join all (escaped) keys from the hashtable into one regular expression.
[regex]$r = @($replacements.Keys | foreach { [regex]::Escape( $_ ) }) -join '|'

[scriptblock]$matchEval = { param( [Text.RegularExpressions.Match]$matchInfo )
  # Return replacement value for each matched value.
  $matchedValue = $matchInfo.Groups[0].Value
  $replacements[$matchedValue]
}

# Perform replace over every line in the file and append to log.
Get-Content $filePath |
  foreach { $r.Replace( $_, $matchEval ) } |
  Add-Content 'C:\log.txt'
于 2013-07-28T23:21:15.447 に答える
5

つまり、150,000 行ごとに 600 個の文字列のいずれかを置換し、1 行につき 1 つの置換操作を実行したいということですか?

はい、それを行う方法はありますが、PowerShell ではありません。少なくとも私には思いつきません。Perlでできます。


メソッド:

  1. キーが何かで、値が何か他のものであるハッシュを構築します。
  2. ハッシュのキーを|で結合します。シンボルを検索し、正規表現で一致グループとして使用します。
  3. 置換では、キャプチャ グループの一致変数を使用してハッシュから値を取得する式を補間します

問題:

苛立たしいことに、PowerShell は一致変数を正規表現の置換呼び出しの外に公開しません。-replace演算子では機能せず、[ regex]::replaceでは機能しません。

Perl では、たとえば次のように実行できます。

$string =~ s/(1|2|3)/@{[$1 + 5]}/g;

これにより、文字列全体の数字 1、2、および 3 に 5 が追加されるため、文字列が「1224526123 [2] [6]」の場合、「6774576678 [7] [6]」になります。

ただし、PowerShell では、これらの両方が失敗します。

$string -replace '(1|2|3)',"$($1 + 5)"

[regex]::replace($string,'(1|2|3)',"$($1 + 5)")

どちらの場合も、$1は null に評価され、式は単純な古い 5 に評価されます。置換の一致変数は、結果の文字列、つまり単一引用符で囲まれた文字列、または二重引用符で囲まれた文字列が評価されるものでのみ意味があります。それらは基本的に、一致変数のように見える単なる後方参照です。確かに、二重引用符で囲まれた文字列の数値の前に$を引用することはできます。そのため、対応する一致グループに評価されますが、それは目的に反します。式に参加することはできません。


ソリューション:

[この回答は元から変更されています。正規表現のメタ文字を含む一致文字列に適合するようにフォーマットされています。もちろん、テレビ画面も。]

別の言語を使用しても構わない場合は、次の Perl スクリプトが魅力的です。

$filePath = $ARGV[0]; # Or hard-code it or whatever
open INPUT, "< $filePath";
open OUTPUT, '> C:\log.txt';
%replacements = (
  'something0' => 'somethingelse0',
  'something1' => 'somethingelse1',
  'something2' => 'somethingelse2',
  'something3' => 'somethingelse3',
  'something4' => 'somethingelse4',
  'something5' => 'somethingelse5',
  'X:\Group_14\DACU' => '\\DACU$',
  '.*[^xyz]' => 'oO{xyz}',
  'moresomethings' => 'moresomethingelses'
);
foreach (keys %replacements) {
  push @strings, qr/\Q$_\E/;
  $replacements{$_} =~ s/\\/\\\\/g;
}
$pattern = join '|', @strings;
while (<INPUT>) {
  s/($pattern)/$replacements{$1}/g;
  print OUTPUT;
}
close INPUT;
close OUTPUT;

ハッシュのキー ( =>の左側) を検索し、それらを対応する値に置き換えます。何が起こっているかは次のとおりです。

  • foreachループは、ハッシュのすべての要素を調べて、%replacements ハッシュのキーを含む @strings という配列を作成します。メタ文字は\ Qおよび\ Eを使用して引用され、その結果は正規表現パターンとして使用するために引用されます。 ( qr = 正規表現を引用)。同じパスで、置換文字列のすべてのバックスラッシュを 2 倍にしてエスケープします。
  • 次に、配列の要素が|で結合されます。の検索パターンを形成します。必要に応じて$patternにグループ化括弧を含めることもできますが、この方法により、何が起こっているのかがより明確になると思います。
  • whileループは、入力ファイルから各行を読み取り、検索パターン内の文字列をハッシュ内の対応する置換文字列に置き換え、その行を出力ファイルに書き込みます。

ところで、元のスクリプトからいくつかの変更が加えられていることに気付いたかもしれません。私の Perl は、最近の PowerShell のキック中にいくらかのほこりを集めました。

  • while (<INPUT>)一度に 1 行ずつファイルを読み取ります。特に目標が効率である場合は、150,000 行全体を配列に読み込むよりもはるかに賢明です。
  • に簡略化@{[$replacements{$1}]}しました$replacements{$1}。Perl には、PowerShell の$()のような式を補間する組み込みの方法がないため、回避策として@{[ ]}が使用されます。これは、式を含む 1 つの要素のリテラル配列を作成します。しかし、式が単一のスカラー変数である場合は必要ないことに気付きました ( $1マッチ変数に計算を適用した最初のテストからの持ち越しとしてそこに入れました)。
  • closeステートメントは厳密には必要ありませんが、ファイルハンドルを明示的に閉じることをお勧めします。
  • for の略語をforeachに変更して、PowerShell プログラマーにとってより明確で親しみやすいものにしました。
于 2013-07-18T06:04:57.230 に答える
2

これをpowershellで解決する方法もわかりませんが、Bashで解決する方法は知っています。それは、sedというツールを使用することです。幸いなことに、 Sed for Windowsもあります。どこでも「something#」を「somethingelse#」に置き換えるだけなら、このコマンドでうまくいきます

sed -i "s/something([0-9]+)/somethingelse\1/g" c:\log.txt

Bash では、これらの文字のいくつかをバックスラッシュで実際にエスケープする必要がありますが、Windows ではエスケープする必要があるかどうかはわかりません。最初のコマンドが不平を言う場合は、試すことができます

sed -i "s/something\([0-9]\+\)/somethingelse\1/g" c:\log.txt
于 2013-07-20T01:16:07.727 に答える
1

私はpowershellスイッチステートメントを使用します:

$string = gc $filePath 
$string | % {
    switch -regex ($_)  {
        'something0' { 'somethingelse0' }
        'something1' { 'somethingelse1' }
        'something2' { 'somethingelse2' }
        'something3' { 'somethingelse3' }
        'something4' { 'somethingelse4' }
        'something5' { 'somethingelse5' }
        'pattern(?<a>\d+)' { $matches['a'] } # sample of more complex logic
   ...
   (600 More Lines...)
   ...
        default { $_ }
   }
} | ac "C:\log.txt"
于 2013-10-22T14:14:28.973 に答える