web-dev-qa-db-ja.com

パターンを再帰的に検索し、一致ごとに特定のシーケンスを出力します:行番号、ファイル名、ファイル内容なし

私が探しているものはここにあるものとほとんど同じですが、結果に「行番号、区切り文字、ファイル名、改行」の形式を使用して、ファイル名の後ではなく、行の先頭に行番号を表示します、および一致を含む行を表示しません。

このフォーマットが望ましい理由は、

  • (a)ファイル名は長くて不可解で、ツールがファイル名と行番号を区切るために使用する区切り文字を含んでいる可能性があるため、信じられないほど難しいファイル内のパターンにも同じセパレータが含まれている可能性があるため、awkを使用してこれを実現します。また、行頭の行番号は、ファイル名の後に表示される場合よりも整列されます。そして、この望ましいフォーマットの他の理由は
  • (b)パターンに一致する行が長すぎる可能性があり、標準出力に表示される(および標準出力での出力は、ファイルに保存し、viなどのツールを使用して、出力ファイルの行ごとに1行を表示するよりも優れています。

    ディレクトリを再帰的に検索してパターンを検索し、ファイル名と行番号を出力する方法

要件を設定したので、次のことを考慮してください。

  1. 使用しているLinuxホストにAckがインストールされていないため、使用できません。

  2. 以下を実行すると、シェルはfind .を実行し、「find。」を現在の作業ディレクトリから始まり、再帰的に進む絶対パスのリストに置き換えます。

    grep -n PATTERN $(find .)
    

    次に、-nは行番号を出力しますが、必要な場所は出力しません。また、何らかの理由で理解できません。ディレクトリ名にPATTERNが含まれている場合、grepは、パターンを含む通常のファイルに加えて、それを照合します。これは私が欲しいものではないので、私は以下を使用します:

    grep -n PATTERN $(find . -type f)
    

    また、このコマンドを変更して、findの出力が動的にgrepに渡されるようにしました。最初に絶対パスのリスト全体を作成してから、それらの大部分をgrepに渡す必要はなく、リストを作成するときに各行をgrepに渡すようにしてください。

    find . -exec grep -n PATTERN  '{}' \;
    

    man pageによると、これは正しい構文のように見えますが、このコマンドを発行すると、Bashシェルの実行が約100倍遅くなるため、これは適切な方法ではありません。

私が説明した内容に照らして、このコマンドに似たものを実行して、目的の形式を取得するにはどうすればよいでしょうか。私はすでに関連する投稿に関連する問題をリストしました。

6
John Sonderson

Grepの使用

grepを使用する代わりに、-rスイッチをfindに再帰的に再帰できないのはなぜですか? -nスイッチの代わりに、使用するスイッチが2つあります。

$ grep -rHn PATTERN <DIR> | cut -d":" -f1-2

例1

$ grep -rHn PATH ~/.bashrc | cut -d":" -f1-2
/home/saml/.bashrc:25

細部

  • -r-ファイルとディレクトリを再帰的に検索します
  • -H-一致する場合はファイル名を出力します(-lより制限が少ない)。つまり、grepの他のスイッチで機能します
  • -n-一致の行番号を表示します

例2

$ grep -rHn PATH ~/.bash* | cut -d":" -f1-2
/home/saml/.bash_profile:10
/home/saml/.bash_profile:12
/home/saml/.bash_profile_askapache:99
/home/saml/.bash_profile_askapache:101
/home/saml/.bash_profile_askapache:118
/home/saml/.bash_profile_askapache:166
/home/saml/.bash_profile_askapache:218
/home/saml/.bash_profile_askapache:250
/home/saml/.bash_profile_askapache:314
/home/saml/.bash_profile_askapache:2317
/home/saml/.bash_profile_askapache:2323
/home/saml/.bashrc:25

検索を使用する

$ find . -exec sh -c 'grep -Hn PATTERN "$@" | cut -d":" -f1-2' {}  +

$ find ~/.bash* -exec sh -c 'grep -Hn PATH "$@" | cut -d":" -f1-2' {}  +
/home/saml/.bash_profile:10
/home/saml/.bash_profile:12
/home/saml/.bash_profile_askapache:99
/home/saml/.bash_profile_askapache:101
/home/saml/.bash_profile_askapache:118
/home/saml/.bash_profile_askapache:166
/home/saml/.bash_profile_askapache:218
/home/saml/.bash_profile_askapache:250
/home/saml/.bash_profile_askapache:314
/home/saml/.bash_profile_askapache:2317
/home/saml/.bash_profile_askapache:2323
/home/saml/.bashrc:25

本当にfindを使用したい場合は、grepを使用してファイルを検索するときに、このようにfindを実行できます。

4
slm
grep -n PATTERN `find . -type f`

コマンド置換の出力は、空白で区切られたファイル名のワイルドカードパターンのリストとして解釈されるため、これは悪いことです。ファイル名に空白または\[*?のいずれかが含まれている場合、このスニペットは機能しません。また、一致するファイルが多数ある場合、最終的にはコマンドラインが長すぎます。

find . -exec grep -n PATTERN  '{}' \;

これは問題なく信頼できますが、grepはファイルごとに1回呼び出されます。これがとても遅い理由です。

-exec … {} +を使用して、できるだけ多くのファイルのバッチでコマンドを実行します。最後のバッチ(または理論的には他のバッチ)が単一のファイルで構成される場合があるので、grepはファイル名を出力しないことに注意してください。 -Hオプションを渡して、常にファイル名を出力するか、引数/dev/nullを追加します(一致するものは含まれませんが、grepが少なくとも2つのファイル名を確認できるようにします)。

find . -type f -exec grep -Hn PATTERN {} +

GNU grepには、一致する行番号を印刷するオプションはありませんが、一致する行テキストを印刷するオプションはありません。一致するテキストを削除し、行番号をファイル名とsedで置き換えることができます。

find . -type f -exec grep -Hn PATTERN {} + | sed 's/^\([^:]*\):\([^:]*\):.*/\2:\1/'

行番号を右揃えにしたい場合、awkは、私が考えることができる他のどの方法よりもはるかに単純です。

find . -type f -exec grep -Hn PATTERN {} + | awk -F : '{printf "%8d:%s", $2, $1}'

Grepの代わりにawkでマッチングを行うことで、より細かく制御できます。 Awkはインタプリタ言語を備えたより汎用的なツールであるため、少し遅くなる傾向があります。 1つの利点は、コロンまたは改行を含むファイル名の処理方法を選択できることです。これにより、grepからのあいまいな出力が発生します。次のスニペットは、awkを使用して検索を実行し、:(および改行も含むが、あいまいな出力を生成する)を含むファイル名を処理します。 awkはgrep -Eのように 拡張正規表現 を使用することに注意してください(わずかな違いはありますが、grepまたはawkの実装間で得られる以上のものではありません)。

find . -type f -exec awk '/PATTERN/ {printf "%d:", FNR; print FILENAME}' {} +