大きなフォルダー階層でテキストを置き換える方法は?


11

いくつかのインスタンスを除いて、ファイルの大規模なセットの一部のテキストを検索して置換したい。各行について、その行を置き換える必要があるかどうかを尋ねるプロンプトが必要です。vimに似ていますが(確認を求めるプロンプト:%s/from/to/gcが表示さcれます)、一連のフォルダーに渡ります。使用できる優れたコマンドラインツールまたはスクリプトはありますか?


適切なフォーマットの重要性:コマンドを最初に読むときは、書き込もうとしたときに強調s/from/to/gするのではなく、フォーマットの不具合のように読みます(Markdownでそれを行うことはできません。およびHTMLタグ)。s/from/to/gcc<code><strong>
Gilles 'SO-邪悪なことをやめなさい'

回答:


19

vimを使用しないのはなぜですか?

vimですべてのファイルを開く

vim $(find . -type f)

または、関連するファイルのみを開く(Calebの提案どおり)

vim $(grep 'from' . -Rl)

そして、すべてのバッファで置換を実行します

:bufdo %s/from/to/gc | update

でもできますsedが、私のsedの知識は限られています。


ありがとう、あなたの答えは私に遅いダブルテイクをさせました:私はインタラクティブなビットを完全に逃したことに気づきました。それはsedでさえ可能ではないと思います(十分な入力/出力チャネルではありません)。
Gilles 'SO-邪悪なことをやめなさい'

1
現在のバッファー内のすべてのファイルを開かgrepず、代わりにを使用findして既知の一致があるファイルのみを開くことで、これを高速化できます。vim $(grep 'from' . -Rl)
カレブ

ありがとう。ccの周りのastreriks)が必要ですか?またはそのフォーマットの問題?
Balki、

@balkiは「フォーマット」の問題です。修正済み
Gert

5

-l -pe引数(-i)として渡されたファイルに対して行ごとに置換()を実行するように指示されている小さなPerlスクリプトを使用して、大雑把なことができます。

perl -i -l -pe '
    if (/from/) {                            # is the source text present on this line?
        printf STDERR ("%s: %s [y/N]? ", $ARGV, $_);  # display a prompt
        $r=<STDIN>;                                   # read user response
        if ($r =~ /^[Yy]/) {                          # if user entered Y:
            s/from/to/g;                              # replace all occurences on this line
    }' /path/to/files

可能な改善点は、プロンプトの一部に色を付け、「現在のファイル内のすべての出現箇所を置き換える」などのサポートを提供することです。行に出現するたびに個別にプロンプ​​トを出すのは困難です。

2番目の部分は、ファイルのマッチングです。関連するファイルが多すぎず、zshを実行している場合は、現在のディレクトリとそのサブディレクトリ内のすべてのファイルを再帰的に照合できます。

perl -i -l -pe '…' **/*(.)

シェルがbash≥4の場合、を実行できますがperl … **/*、sedはディレクトリでの実行を試行(および失敗)するため、誤ったエラーメッセージが生成されます。Cファイルなどの一連のファイルで置換のみを実行する場合は、一致を制限できます(これは、bash≥4またはzshで機能します)。

perl -i -l -pe '…' **/*.[hc]

置き換えるファイルを細かく制御する必要がある場合、またはシェルに再帰的なディレクトリマッチングコンストラクト**がない場合、またはファイルが多すぎて「コマンドラインが長すぎる」エラーが発生する場合は、を使用してくださいfind。たとえば、名前が付けられたすべてのファイル、*.hまたは*.c現在のディレクトリとそのサブディレクトリで置換を実行するには(古いシステム\;+は、行末ではなくを使用する必要があります(+フォームは高速ですが、どこでも使用できるわけではありません)。

find . -type f -name '*.[hc]' -exec perl -i -l -pe '…' {} +

とはいえ、対話が必要な場合は、対話型エディターを使用します。GertはVimこれを行う方法を示しましたが、検索するすべてのファイルを開く必要があるため、たくさんある場合は問題になる可能性があります。

Emacsでは、これを行う方法は次のとおりです。

  1. M-x find-name-dired(トップレベルディレクトリをM-x find-dired指定)または(任意のfindコマンドラインを指定)を使用してファイル名を収集します。
  2. 作成されたdiredバッファで、を押しtてすべてのファイルをマークし、次にQdired-do-query-replace-regexpを押してマークされたファイルを要求する置換を実行します。

1

sdiffhttp://www.gnu.org/software/diffutils/manual/diffutils.html#Invoking-sdiffを参照してください)ここで重宝するかもしれません。これを使用すると、インタラクティブなパッチを適用できます。したがって、置換操作を実行して作成した一時ファイルを使用してそれを行うことはsed、可能な解決策になる可能性があります。

# use file descriptor 3 to still allow use of stdin
while IFS= read -r -d '' file <&3; do

  # write the result of the replacement into a temporary file
  sed -r 's/something/something_else/g' -- "$file" > replacer_tmp

  if cmp -s -- "$file" replacer_tmp; then
    continue; # nothing was replaced
  fi

  echo "There is something to replace in '$file'! Starting interactive diff."
  echo

  sdiff -o "$file" -s -d -- "$file" replacer_tmp

  echo

done 3< <(find . -type f -print0)

(非POSIXプロセス置換を使用しread -d、サポートされてbashいるファイルループなど)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.