web-dev-qa-db-ja.com

grepを使って複数行にわたるパターンを見つける方法は?

"abc"と "efg"をこの順序で含むファイルを見つけたいのですが、この2つの文字列はそのファイルの異なる行にあります。例:内容を含むファイル:

blah blah..
blah blah..
blah abc blah
blah blah..
blah blah..
blah blah..
blah efg blah blah
blah blah..
blah blah..

一致するはずです。

183
Saobi

Grepはこの操作には不十分です。

pcregrep 最近のほとんどのLinuxシステムに見られるものは、

pcregrep -M  'abc.*(\n|.)*efg' test.txt

-M、 - multilineを使用すると、パターンを複数行に一致させることができます。

新しい pcre2grep もあります。どちらも PCREプロジェクト によって提供されています。

pcre2grepは、Mac OS Xで Mac Ports からpcre2の一部として利用可能です:

% Sudo port install pcre2 

そしてvia 自作 として

% brew install pcre

またはpcre2用

% brew install pcre2
192
ring bearer

Grepでそれが可能かどうかはわかりませんが、sedによって非常に簡単になります。

sed -e '/abc/,/efg/!d' [file-with-content]
105
LJ.

これは この答え に触発された解決策です。

  • 'abc'と 'efg'を同じ行に入れることができる場合

    grep -zl 'abc.*efg' <your list of files>
    
  • 'abc'と 'efg'が別々の行になければならない場合:

    grep -Pzl '(?s)abc.*\n.*efg' <your list of files>
    

Params:

  • -z入力を一連の行として扱い、各行は改行ではなくゼロバイトで終了します。すなわち、grepは入力を1つの大きな線として脅かします。

  • -l出力が通常印刷される各入力ファイルの名前。

  • (?s)はPCRE_DOTALLをアクティブにします。これは「。」を意味します。文字または改行を見つけます。

72
atti

sedはLJが上記のように十分であるべきです、

!dの代わりに、単にpを使って印刷することができます。

sed -n '/abc/,/efg/p' file
29
user3897784

私はpcregrepに大きく依存していましたが、より新しいgrepを使えば、その多くの機能のためにpcregrepをインストールする必要はありません。 grep -Pを使うだけです。

OPの質問の例では、次のオプションがうまく機能していると思います。2番目の質問は、私が質問を理解する方法と一致しています。

grep -Pzo "abc(.|\n)*efg" /tmp/tes*
grep -Pzl "abc(.|\n)*efg" /tmp/tes*

テキストを/ tmp/test1としてコピーし、 'g'を削除して/ tmp/test2として保存しました。これは、最初の文字列が一致した文字列を示し、2番目の文字列がファイル名のみを示していることを示す出力です(通常の-oは一致を示し、通常の-lはファイル名のみを示します)。 'z'は複数行に必要で、 '(。|\n)'は '改行以外の何か'または '改行'のいずれかに一致することを意味します。

user@Host:~$ grep -Pzo "abc(.|\n)*efg" /tmp/tes*
/tmp/test1:abc blah
blah blah..
blah blah..
blah blah..
blah efg
user@Host:~$ grep -Pzl "abc(.|\n)*efg" /tmp/tes*
/tmp/test1

あなたのバージョンが十分に新しいかどうかを判断するには、man grepを実行して、これに似たものが上部近くに表示されるかどうか確認してください。

   -P, --Perl-regexp
          Interpret  PATTERN  as a Perl regular expression (PCRE, see
          below).  This is highly experimental and grep -P may warn of
          unimplemented features.

これはGNU grep 2.10からです。

12
sage

これは最初にtrを使って改行を他の文字に置き換えることで簡単に行えます。

tr '\n' '\a' | grep 'abc.*def' | tr '\a' '\n'

ここでは、改行の代わりに\a(ASCII 7)というアラーム文字を使用しています。これはあなたのテキストにはほとんど見られず、grepはそれを.と突き合わせることも、\aと突き合わせることもできます。

9
g.rocket

あなたがPerlを使うことができれば、あなたはそれを非常に簡単にすることができます。

Perl -ne 'if (/abc/) { $abc = 1; next }; print "Found in $ARGV\n" if ($abc && /efg/); }' yourfilename.txt

これは単一の正規表現でも可能ですが、ファイルの内容全体を単一の文字列にまとめることになります。完全を期すために、これがその方法です。

Perl -e '@lines = <>; $content = join("", @lines); print "Found in $ARGV\n" if ($content =~ /abc.*efg/s);' yourfilename.txt
6
sundar

Grepを使ってどうするかわかりませんが、awkを使って次のようにします。

awk '/abc/{ln1=NR} /efg/{ln2=NR} END{if(ln1 && ln2 && ln1 < ln2){print "found"}else{print "not found"}}' foo

ただし、これを実行する方法には注意が必要です。正規表現を部分文字列または単語全体と一致させますか?必要に応じて\ wタグを追加してください。また、これは厳密に例を示した方法に準拠していますが、abcがefgの後に2回目に表示されたときにはまったく機能しません。それを処理したい場合は、/ abc /の場合などにifを追加してください。

5
frankc

ワンライナーをawk:

awk '/abc/,/efg/' [file-with-content]
5
Swynndla

私は数日前に、複数行のマッチングや条件を使って直接これをサポートするgrepの選択肢をリリースしました。これは、例のコマンドが次のようになることです。

複数行:sift -lm 'abc.*efg' testfile
条件:sift -l 'abc' testfile --followed-by 'efg'

また、 'efg'が一定の行数内で 'abc'の後に続くように指定することもできます。
sift -l 'abc' testfile --followed-within 5:'efg'

あなたは sift-tool.org でより多くの情報を見つけることができます。

3
svent

残念ながら、できません。 grepのドキュメントから:

grepは、名前付き入力ファイル(ファイル名がない場合、または単一のハイフンマイナス( - )がファイル名として指定されている場合は標準入力)から、指定したパターンとの一致を含むlinesを検索します。

3
Kaleb Pederson

両方の単語を互いに接近させる必要がある場合、たとえば3行以内であれば、これを実行できます。

find . -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

同じ例ですが* .txtファイルのみをフィルタリングします。

find . -name *.txt -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

また、正規表現で検索したい場合は、grepコマンドをegrepコマンドに置き換えることもできます。

2
Mariano Ruiz

あなたがコンテキストを使用しても構わないと思っているのであれば、これは次のように入力することで達成できます。

grep -A 500 abc test.txt | grep -B 500 efg

これは、お互いの500行以内にある限り、すべてのbetween "abc"と "efg"を表示します。

2
agouge

Sedオプションが最も簡単で簡単ですが、LJのワンライナーは残念ながら最もポータブルではありません。 Cシェルのバージョンで立ち往生しているものは、彼らの強打を脱出する必要があります。

sed -e '/abc/,/efg/\!d' [file]

残念ながらこれはbash et al。ではうまくいきません。

2
bug

あなたがパターンのシーケンスに熱心でない場合は、grepを使うことができます。

grep -l "pattern1" filepattern*.* | xargs grep "pattern2"

grep -l "vector" *.cpp | xargs grep "map"

grep -lは最初のパターンにマッチするすべてのファイルを見つけ、xargsは2番目のパターンをgrepします。お役に立てれば。

1
Balu Mohan

シルバーサーチャー

ag 'abc.*(\n|.)*efg'

リングベアラの答えと似ていますが、代わりにagを使用します。銀の検索者のスピードの利点は、おそらくここで輝く可能性があります。

1
Shwaydogg
#!/bin/bash
shopt -s nullglob
for file in *
do
 r=$(awk '/abc/{f=1}/efg/{g=1;exit}END{print g&&f ?1:0}' file)
 if [ "$r" -eq 1 ];then
   echo "Found pattern in $file"
 else
   echo "not found"
 fi
done
1
ghostdog74

Grepの-Pオプションを使用して、マルチファスタファイルからファスタシーケンスを抽出するためにこれを使用しました。

grep -Pzo ">tig00000034[^>]+" file.fasta > desired_sequence.fasta

-P Perlベースの検索では-P改行char -oではなく0バイトで行末を作るための-z grepは行全体を返すので(この場合は-zがファイル全体であるため)一致したものだけをキャプチャします。正規表現の中核は[^>]で、これは「シンボル以下」と解釈されます。

1
Jon Boyle

ファイルパターン*.shは、ディレクトリが検査されないようにするために重要です。もちろん、いくつかのテストでもそれを防ぐことができます。

for f in *.sh
do
  a=$( grep -n -m1 abc $f )
  test -n "${a}" && z=$( grep -n efg $f | tail -n 1) || continue 
  (( ((${z/:*/}-${a/:*/})) > 0 )) && echo $f
done

grep -n -m1 abc $f 

最大で1つ一致するものを検索し、行番号を返します(-n)。一致が見つかった場合(test -n ...)、efgの最後の一致を見つけます(すべてを見つけてtail -n 1で最後に進みます)。

z=$( grep -n efg $f | tail -n 1)

それ以外の場合は続行します。

結果は18:foofile.sh String alf="abc";のようなものになるので、行末まで ":"から切り捨てる必要があります。

((${z/:*/}-${a/:*/}))

2番目の式の最後の一致が最初の一致の最初の一致を過ぎている場合は、正の結果を返します。

次に、ファイル名echo $fを報告します。

0
user unknown

あなたが探している2つの文字列 'abc'と 'efg'の間の距離についていくらかの見積もりがあるなら、あなたは使用するかもしれません:

grep -r -e 'abc' -A num1 -B num2 | grep 'efg'

このように、最初のgrepは 'abc'とそれに続く#num1行、その後に#num2行の行を返し、2番目のgrepはそれらのすべてを見て 'efg'を取得します。その後、どのファイルにそれらが一緒に表示されるかがわかります。

0
Benjamin Berend

Balu Mohanの答えに代わるものとして、grepheadおよびtailのみを使用してパターンの順序を強制することが可能です。

for f in FILEGLOB; do tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep "pattern2" &>/dev/null && echo $f; done

これはそれほどきれいではありません。読みやすくフォーマットされた

for f in FILEGLOB; do
    tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null \
    | grep -q "pattern2" \
    && echo $f
done

これは"pattern2"の後に"pattern1"が現れるすべてのファイルの名前を表示しますまたは両方が同じ行に現れる場合

$ echo "abc
def" > a.txt
$ echo "def
abc" > b.txt
$ echo "abcdef" > c.txt; echo "defabc" > d.txt
$ for f in *.txt; do tail $f -n +$(grep -n "abc" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep -q "def" && echo $f; done
a.txt
c.txt
d.txt

説明

  • tail -n +i - ith以降のすべての行を表示
  • grep -n - 一致する行の先頭に行番号を付ける
  • head -n1 - 最初の行だけを印刷する
  • cut -d : -f 1 - :を区切り文字として使用して最初のカット列を印刷する
  • 2>/dev/null - $()式が空を返す場合に発生するtailエラー出力を黙らせる
  • grep -q - grepを黙らせ、一致が見つかった場合はただちに戻ります。終了コードだけに関心があるためです。
0
Emil Lundberg

これでうまくいくでしょうか。

Perl -lpne 'print $ARGV if /abc.*?efg/s' file_list

$ARGVは、file_list /s修飾子からの読み込みが改行を越えて検索されるときの現在のファイルの名前を含みます。

0
PS12