バルクファイルテキストの置換を適切に行う簡単な方法はありますか?
-
05-07-2019 - |
質問
Perlスクリプトをコーディングして、プロジェクトのすべてのソースファイルの一部のテキストを置換しようとしています。次のようなものが必要です:
perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" *.{cs,aspx,ascx}
ただし、ディレクトリのファイルをすべて解析する再帰的に。
スクリプトを開始しました:
use File::Find::Rule;
use strict;
my @files = (File::Find::Rule->file()->name('*.cs','*.aspx','*.ascx')->in('.'));
foreach my $f (@files){
if ($f =~ s/thisgoesout/thisgoesin/gi) {
# In-place file editing, or something like that
}
}
しかし今、私は立ち往生しています。 Perlを使用してすべてのファイルをインプレースで編集する簡単な方法はありますか?
すべての変更されたファイルのコピーを保持する必要がないことに注意してください。私はすべてのバージョンを破壊しました=)
更新: Cygwin でこれを試しました
perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" {*,*/*,*/*/*}.{cs,aspx,ascx
しかし、私の引数リストは許可された最大サイズまで爆発したようです。実際、Cygwinで非常に奇妙なエラーが発生しています...
解決
@ARGV
(別名、ダイアモンド*ARGV
)を使用する前に<>
を割り当てると、コマンドラインで指定されたものではなく、これらのファイルに対して$^I
/ -i
が機能します。
use File::Find::Rule;
use strict;
@ARGV = (File::Find::Rule->file()->name('*.cs', '*.aspx', '*.ascx')->in('.'));
$^I = '.bak'; # or set `-i` in the #! line or on the command-line
while (<>) {
s/thisgoesout/thisgoesin/gi;
print;
}
これはまさにあなたが望むことをするはずです。
パターンが複数行にわたる場合は、undef $/;
の前に<=>を追加して、Perlが行ごとではなくファイル全体を一度に操作するようにします。
他のヒント
File :: Transaction :: Atomic または File :: Transaction
F :: T :: Aの概要は、あなたがやろうとしていることと非常によく似ています:
# In this example, we wish to replace
# the word 'foo' with the word 'bar' in several files,
# with no risk of ending up with the replacement done
# in some files but not in others.
use File::Transaction::Atomic;
my $ft = File::Transaction::Atomic->new;
eval {
foreach my $file (@list_of_file_names) {
$ft->linewise_rewrite($file, sub {
s#\bfoo\b#bar#g;
});
}
};
if ($@) {
$ft->revert;
die "update aborted: $@";
}
else {
$ft->commit;
}
File :: Findを使用して、すでに記述しているので、準備ができているはずです。
Tie :: Fileを使用して、大規模ファイルにスケーラブルにアクセスし、その場で変更できます。マンページ(man 3perl Tie :: File)を参照してください。
変更
foreach my $f (@files){
if ($f =~ s/thisgoesout/thisgoesin/gi) {
#inplace file editing, or something like that
}
}
宛先
foreach my $f (@files){
open my $in, '<', $f;
open my $out, '>', "$f.out";
while (my $line = <$in>){
chomp $line;
$line =~ s/thisgoesout/thisgoesin/gi
print $out "$line\n";
}
}
これは、パターンが複数行にまたがらないことを前提としています。パターンが複数行にわたる場合は、ファイルの内容を丸lurみする必要があります。 (<!> quot; slurp <!> quot;はかなり一般的なPerlの用語です。)
chompは実際には必要ではありません。chomp
1回あまり行かれていない行に噛まれただけです(print $out "$line\n";
をドロップした場合、print $out $line;
をopen my $out, '>', "$f.out";
に変更します)。
同様に、open my $out, '>', undef;
を<=>に変更して一時ファイルを開き、置換が完了したらそのファイルを元のファイルにコピーして戻すことができます。実際、特にファイル全体を丸lurみした場合は、メモリ内で置換を行ってから元のファイルに上書きできます。しかし、それを行うのに十分なミスを犯しているので、常に新しいファイルに書き込み、内容を検証します。
注、元々そのコードにはifステートメントがありました。それはおそらく間違っていた。これは、正規表現<!> quot; thisgoesout <!> quot;に一致する行にのみコピーされます。 (もちろん<!> quot; thisgoesin <!> quot;に置き換えます)、静かに残りをゴブリングします。
find
を使用できます:
find . -name '*.{cs,aspx,ascx}' | xargs perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi"
これはすべてのファイル名を再帰的に一覧表示し、xargs
はその標準入力を読み取り、ファイル名を末尾に追加してコマンドラインの残りを実行します。 -i
の良い点の1つは、ビルドするコマンドラインが一度に実行するには長すぎる場合、コマンドラインを複数回実行することです。
ファイルを選択するすべてのシェルメソッドを<=>が完全に理解しているかどうかわからないので、上記が機能しない場合は、次のことを試してください:
find . | grep -E '(cs|aspx|ascx)$' | xargs ...
このようなパイプラインを使用する場合、各プログラムが必要な入力を得ていることを確認するために、コマンドラインを作成し、各部分を個別に実行してから続行します。したがって、最初に<=>を指定せずにパーツを実行して、チェックすることができます。
そうは言っていませんが、探しているファイルの接尾辞のためにWindowsを使用している可能性があります。その場合、上記のパイプラインはCygwinを使用して実行できます。あなたが始めたのと同じことをするPerlスクリプトを書くことは可能ですが、そのような状況では<=>スイッチを利用できないため、自分でインプレース編集を行う必要があります。
この質問と thisについてエフェミエントに感謝します。回答、私はこれを得ました:
use File::Find::Rule;
use strict;
sub ReplaceText {
my $regex = shift;
my $replace = shift;
@ARGV = (File::Find::Rule->file()->name('*.cs','*.aspx','*.ascx')->in('.'));
$^I = '.bak';
while (<>) {
s/$regex/$replace->()/gie;
print;
}
}
ReplaceText qr/some(crazy)regexp/, sub { "some $1 text" };
今では、regexp = <!> gt; subsエントリを含むハッシュをループすることもできます!