web-dev-qa-db-ja.com

2つの異なる単語が存在するファイルを検索する方法は?

同じファイル内に2つのWordインスタンスが存在するファイルを検索する方法を探しています。これまで、次の方法で検索を行ってきました。

find . -exec grep -l "FIND ME" {} \;

私が遭遇している問題は、「FIND」と「ME」の間に1つのスペースがない場合、検索結果がファイルを生成しないことです。 「FIND ME」ではなく「FIND」と「ME」の両方の単語がファイルに存在する以前の検索文字列をどのように適応させるのですか?

AIXを使用しています。

14
Chad Harrison

GNUツール:

find . -type f  -exec grep -lZ FIND {} + | xargs -r0 grep -l ME

あなたは標準的に行うことができます:

find . -type f -exec grep -q FIND {} \; -exec grep -l ME {} \;

しかし、これはファイルごとに最大2つのgrepsを実行します。このように多くのgrepsの実行を回避し、ファイル名に任意の文字を許可しながら移植性を維持するには、次のようにします。

convert_to_xargs() {
  sed "s/[[:blank:]\"\']/\\\\&/g" | awk '
    {
      if (NR > 1) {
        printf "%s", line
        if (!index($0, "//")) printf "\\"
        print ""
      }
      line = $0
    }'
    END { print line }'
}

export LC_ALL=C
find .//. -type f |
  convert_to_xargs |
  xargs grep -l FIND |
  convert_to_xargs |
  xargs grep -l ME

findの出力をxargs(CロケールではSPC/TAB/NL、他のロケールではYMMV)で区切られた単語のリストに変換して、単一引用符、二重引用符、バックスラッシュで空白をエスケープできる形式に変換するというアイデアとお互い)。

通常、find -printの出力を後処理することはできません。これは、ファイル名を改行文字で区切り、ファイル名に含まれる改行文字をエスケープしないためです。たとえば、次の場合:

./a
./b

a<NL>.というディレクトリにあるbという1つのファイルか、現在のディレクトリにあるabという2つのファイルかどうかを確認する方法がありません。

.//.を使用することにより、//は、findによる出力としてファイルパスに表示されません(名前が空のディレクトリはなく、/はファイルで許可されていないため) name)、//を含む行が見つかれば、それが新しいファイル名の最初の行であることがわかります。したがって、そのawkコマンドを使用して、すべての改行文字をエスケープし、それらの行の前にある文字をエスケープすることができます。

上記の例の場合、最初のケースではfindが出力されます(1つのファイル)。

.//a
./b

どのawkがエスケープするか:

.//a\
./b

つまり、xargsはそれを1つの引数と見なします。 2番目のケース(2つのファイル):

.//a
.//b

どのawkがそのまま残るので、xargsは2つの引数を参照します。

21

ファイルが単一のディレクトリにあり、その名前にスペース、タブ、改行、*?または[文字が含まれておらず、-で始まっていない場合.でも、MEを含むファイルのリストを取得し、FINDも含むファイルに絞り込みます。

grep -l FIND `grep -l ME *`
8
user45529

awkを使用すると、次のコマンドも実行できます。

find . -type f  -exec awk 'BEGIN{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; END{if (cx > 0 && cy > 0) print FILENAME}' {} \;

cxcyを使用して、FINDMEにそれぞれ一致する行をカウントします。 ENDブロックでは、両方のカウンターが0より大きい場合、FILENAMEを出力します。
これはgnu awk

find . -type f  -exec gawk 'BEGINFILE{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; ENDFILE{if (cx > 0 && cy > 0) print FILENAME}' {} +
3
don_crissti

または、次のように_egrep -e_または_grep -E_を使用します。

find . -type f -exec egrep -le '(ME.*FIND|FIND.*ME)' {} \;

または

find . -type f -exec grep -lE '(ME.*FIND|FIND.*ME)' {} +

_+_は、_-exec_ edであるコマンドの引数として複数のファイル(パス)名を検索(サポートされている場合)に追加します。これはプロセスを節約し、見つかったファイルごとにコマンドを呼び出す_\;_よりもはるかに高速です。

_-type f_は、ディレクトリでのgrepを回避するために、ファイルのみに一致します。

'(ME.*FIND|FIND.*ME)'は、「ME」の後に「FIND」が続く、または「FIND」の後に「ME」が続くすべての行に一致する正規表現です。 (シェルが特殊文字を解釈しないようにするための単一引用符)。

_-i_をgrepコマンドに追加して、大文字と小文字を区別しないようにします。

「FIND」が「ME」の前にある行のみを一致させるには、_'FIND.*ME'_を使用します。

単語の間にスペース(1つ以上、ただしそれ以外)を必要とする場合:_'FIND +ME'_

単語間にスペース(0以上、ただしそれ以外)を許可するには:_'FIND *ME'_

正規表現を使用した組み合わせは無限であり、一度に1行ずつのみのマッチングに関心がある場合、egrepは非常に強力です。

2
MattBianco

受け入れられた答えを見ると、必要以上に複雑に見えます。 GNU findgrepxargsのバージョンは、NULLで終了する文字列をサポートしています。これは次のように簡単です:

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l ME

findコマンドを変更して、目的のファイルにフィルタリングできます。これは、任意の文字を含むファイル名で機能します。 sed解析の複雑さを追加する必要はありません。ファイルをさらに処理する場合は、最後のgrepに別の--nullを追加します

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l --null ME | xargs -0 echo

そして、関数として:

find_strings() {
    find . -type f -print0 | xargs -0 grep -l --null "$1" | xargs -0 grep -l "$2"
}

明らかに、これらのツールのGNUバージョンを実行していない場合は、受け入れられた回答を使用してください。

0
razzed