グローバルが選択されていない場合のPerl正規表現グローバル置換

グローバルが選択されていない場合のPerl正規表現グローバル置換

私はUbuntu 11.04を使用しており、テキストファイルから特定の「タグ」を検索し、それを同じ名前のテンプレートファイルから事前に作成された一部に置き換える小さなスクリプトを作成しました。

検索されるテキストファイルには各タグのインスタンスが2つしかありません。 1つ目はプレーンテキストで、2つ目は各バージョンごとに別々のスニペットを含むhtmlバージョンです。

スクリプトは次のとおりです。

for f in `ls -1 .templates/template_text`;
do
    g=`cat .templates/template_text/$f`
    find to_process/ -type f | xargs perl -i.old -p -e "s/$f/$g/";
done

for f in `ls -1 .templates/template_html`;
do
    g=`cat .templates/template_html/$f`
    find to_process/ -type f | xargs perl -i.old -p -e "s/$f/$g/g";
done

最初の正規表現で「グローバル」を指定しなかったにもかかわらず、両方のタグを置き換えるという問題が発生しました。これが私がPerlを呼び出す方法によるのか、バグなのか、それとも別のものなのかはわかりません。

どんな助けでも大変感謝します。

更新:Perlの代わりにsedを使用してスクリプトを機能させることができました。

for f in `ls -1 .templates/template_text`;
do
    g=`cat .templates/template_text/$f`
    h=`cat .templates/template_html/$f`
    find to_process/ -type f -print0 | xargs -0 -I {} sed -i -e "0,/$f/s/$f/$g/" -e "0,/$f/s/$f/$h/" {}
done

しかし、Perlコマンドを使って動作させる方法にはまだ興味があります。

答え1

これは、Perlがテキストファイルを一度に1行ずつ読み、各行に置換パターンを適用するためです。したがって、タグが別の行に複数回表示されると、すべてが置き換えられます。

ファイルの最初の項目のみを置き換えるには、-0入力レコード区切り文字をNULL文字に設定し、置換を実行する前にperlにファイル全体を読み取るオプションを追加できます。

答え2

s/$f/$g/$f各行で最初に表示されるbyを置き換えます。ファイル全体の最初の項目$gだけを置き換えるには、こう言う必要があります。$fこれがあなたがすることですsed(最初の発生を含む最大に置き換えます0,/$f/ s/$f/$g/)。 Perlでは、より冗長ですが理解しやすい方法で書くことができます(注:以下の引用の問題を参照)。$f$g$f

perl -i -pe 'if ($n==0) {s/$f/$g/; $n=1;} elsif ($n==1) {s/$f/$h/; $n=2}'

コードにはいくつかの引用問題があります。ファイル名にスペース、ワイルドカード、または印刷できない文字(現在のロケールに存在しないバイトシーケンスなど)が含まれていると問題が発生します。幸いなことに、これらの問題は簡単に解決できます。

まず、いくつかの一般的なシェルの質問です。"$foo"変数の置換とコマンドの置換には常に二重引用符を使用してください。"$(foo)"なぜ引用しないままにしておくべきなのか分からない限り。結合しないと、結果はスペースを含む別々の単語に分割され、各単語はglobパターンとして扱われます。したがって、変数にスペースで区切られたglobパターンのリストが含まれていない限り、それを二重引用符で囲みます。また、代わりに$(…)内部`…`的に入れ子になった引用符を使用することをお勧めします。これは同じですが`…`信頼できず、`混乱しやすいです。'

解析されていない出力ls。ディレクトリ内のすべてのファイルに対して作業を実行する必要がある場合、シェルには使用可能な組み込み構成であるglobbingがあります。代わりに$(ls /path/to/directory)/path/to/directory/*これによりディレクトリパスを含むファイル名が生成されます。これはほとんど常に必要です。そうでない場合は、事前にcd呼び出すか、ディレクトリの全部または一部を削除できます。以下では、を使用します。${f#*/*/}これは、$f最も短い接頭辞マッチングを削除することを意味します。*/*/

for f in .templates/template_text/*; do
  g=$(cat "$f")
  h=$(cat ".templates/template_html/${f#*/*/}")
  find to_process/ -type f …
done

を使用すると、findより簡単な構成を使用することもできますが、作品と組み合わせることも-execできます。を生成しない特別な方法で入力が参照されると予想されるため、なしを使用しないでください。-print0xargs -0xargs-0find

find to_process/ -type f -exec perl … {} +

次の問題は、sedまたはperlの正規表現に文字列を直接挿入することです$f。これは間違っています。この変数には、引用符で囲まれた区切り文字を含む正規表現は含まれていません(両方の場合)。 sedでは、文字列を1回引用し、inの前とinと前にバックスラッシュを追加する必要があります。 Perlを使用すると、より簡単な方法があります。環境を介して値を渡し、Perlに正規表現ではなく文字列があることを知らせます。$g$h//*.\[$f\&/$g$h

export f g h
find to_process/ -type f -exec perl -i -e '
    if ($n==0) {s/\Q$ENV{f}/$ENV{g}/; $n=1;}
    elsif ($n==1) {s/\Q$ENV{f}/$ENV{h}/; $n=2}}
' {} +

関連情報