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


208

「abc」と「efg」がこの順序で含まれているファイルを検索したいのですが、その2つの文字列はそのファイルの異なる行にあります。例:コンテンツを含むファイル:

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

一致する必要があります。


回答:


225

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

最近のほとんどのLinuxシステムにあるpcregrepは、次のように使用できます。

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

ここ-M--multiline 、パターンが複数の行に一致することを許可します

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

pcre2grepは、Mac OS X ではポートの一部としてMacポート経由で利用できますpcre2

% sudo port install pcre2 

そしてHomebrew経由で:

% brew install pcre

またはpcre2の場合

% brew install pcre2

pcre2grepはLinux でも利用できます(Ubuntu 18.04+)

$ sudo apt install pcre2-utils # PCRE2
$ sudo apt install pcregrep    # Older PCRE

11
@StevenLu- -M, --multilineパターンが複数の行に一致することを許可します。
リングベアラー、

7
。*(\ n |。)*は(\ n |。)*と同等であり、後者は短いことに注意してください。さらに、私のシステムでは、長いバージョンを実行すると「pcre_exec()エラー-8」が発生します。代わりに 'abc(\ n |。)* efg'を試してください!
daveagp 2013

6
あなたは、その場合の例では表現非貪欲を行う必要があります'abc.*(\n|.)*?efg'
ベアラリング

4
そして、あなたは最初を省略することができます.*-> 'abc(\n|.)*?efg'正規表現を短くするために(そして知識を増やすために)
Michi

6
pcregrep物事は簡単になりますが、grepあまりにも機能します。たとえば、stackoverflow.com
Michael Miorを

113

grepでそれが可能かどうかはわかりませんが、sedを使用すると非常に簡単になります。

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

4
これはファイルを検出せず、単一のファイルから一致する部分を返します
shiggity

11
@Lj。このコマンドについて説明してもらえますか?私はに精通していますがsed、そのような表現を見たことがない場合は、
アンソニー

1
@Anthony、それはsedのmanページのアドレスの下に文書化されています。/ abc /&/ efg /がアドレスであることを理解することが重要です。
イカ

49
この回答がもう少し説明があれば役に立ったと思います。その場合は、もう一度投票しました。私は少しsedを知っていますが、この答えを使用して30分いじった後に意味のある終了コードを生成するには不十分です。ヒント:以前のコメントにあるように、「RTFM」がStackOverflowで賛成票を獲得することはほとんどありません。
Michael Scheper 14年

25
例による簡単な説明:sed '1,5d':1と5の間の行を削除します。sed '1,5!d':1と5の間ではない行を削除します(つまり、間に行を保持します)。 / pattern /で行を検索します。以下の簡単な例も参照してください。sed -n '/ abc /、/ efg / p' pは印刷用で、-nフラグはすべての行を表示しません
phil_w

86

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

  • 「abc」と「efg」が同じ行にある場合:

    grep -zl 'abc.*efg' <your list of files>
  • 「abc」と「efg」が異なる行にある必要がある場合:

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

パラメータ:

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

  • -l 通常は出力が出力される各入力ファイルの名前を出力します。

  • (?s)PCRE_DOTALLをアクティブにします。つまり、「。」任意の文字または改行を検索します。


@syntaxerrorいいえ、それは単なる小文字だと思いますl。私の知る限り、番号の-1オプションはありません。
スパーホーク2014年

結局のところ、あなたは正しいと思われます。テスト中にタイプミスをしたのかもしれません。いずれにせよ、誤った道を敷いてすみません。
syntaxerror

6
これは素晴らしいです。これについて一つだけ質問があります。-zオプションが改行を扱うようにgrepを指定する場合、zero byte charactersなぜ(?s)正規表現でが必要なのですか?すでに非改行文字である.場合、直接一致させることはできませんか?
Durga Swaroop、2016

1
-z(別名--null-data)および(?s)は、複数行を標準のgrepと一致させるために必要なものです。MacOSの人は、システムで-zまたは--null-dataオプションが利用できることについてコメントを残してください!
Zeke Fast

4
-zは間違いなくMacOSでは利用できません
Dylan Nicholson

33

sedは上記のポスターLJで十分ですが、

!dの代わりに、単にpを使用して印刷できます。

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

16

私は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からです。


14

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

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

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


1
これは私のアプローチでしたが、私は使用\0していたので、必要に応じgrep -aてマッチングを行い\x00ました。echo $log | tr '\n' '\0' | grep -aoE "Error: .*?\x00Installing .*? has failed\!" | tr '\0' '\n'isecho $log | tr '\n' '\a' | grep -oE "Error: .*?\aInstalling .*? has failed\!" | tr '\a' '\n'
チャーリーゴリチャナズ2017年

1
を使用しgrep -oます。
kyb

7

awkワンライナー:

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

4
これはabc、終了パターンがファイルに存在しない場合、または最後の終了パターンがない場合に、ファイルの最後から最後まで問題なく印刷されます。これは修正できますが、スクリプトがかなり複雑になります。
tripleee 2013年

/efg/出力から除外する方法は?
kyb

6

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

見つかった2番目の回答は、数行に一致する複数行ブロック全体を抽出するのに役立ちました.*?。最小一致を取得するには、貪欲でない一致()を使用する必要がありました。
RichVel

5

grepでそれを行う方法はわかりませんが、awkで次のようにします。

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

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


3

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

grepは、指定されたPATTERNとの一致を含むについて、指定された入力FILE(またはファイルが指定されていない場合、または単一のハイフンマイナス(-)がファイル名として指定されている場合は標準入力)を検索します。


何についてgrep -Pz
ナバロ

3

コンテキストを使用する場合は、次のように入力してこれを実現できます。

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

これにより、「abc」と「efg」の間のすべてが表示されます。


3

両方の単語が互いに接近している必要がある場合、たとえば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"

また、正規表現で検索する場合は、grepcommandをegrepcommandに置き換えることもできます。


3

数日前に、複数行マッチングまたは条件を使用してこれを直接サポートする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で詳細を確認できます。


最初の例はうまくいかないと思いますsift -lm 'abc.*efg' testfile。なぜなら、マッチは貪欲efgで、ファイルの最後まですべての行をゴブリンと飲み込むからです。
アレックスRE

2

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

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

残念ながら、これはbashらでは機能しません。


1
#!/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

パターンのシーケンスに詳しくない場合は、grepを使用できます。

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

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

grep -l最初のパターンに一致するすべてのファイルを検索し、xargsは2番目のパターンに対してgrepを実行します。お役に立てれば。


1
ただし、 "pattern1"と "pattern2"の順序は無視されますが、OPは "pattern1"の後に "pattern2"が出現するファイルのみが一致するように指定しています。
Emil Lundberg、2013

1

銀の探索

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

リングベアラーの回答に似ていますが、代わりにgを使用します。シルバーサーチャーの速度の利点は、おそらくここで発揮できます。


1
これは機能していないようです。(echo abctest; echo efg)|ag 'abc.*(\n|.)*efg'一致しません
phiresky 2016

1

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

grep -Pzo ">tig00000034[^>]+"  file.fasta > desired_sequence.fasta
  • Perlベースの検索の場合はP
  • 改行文字ではなく0バイトで行を終了するz
  • o grepは行全体(この場合は-zを実行したのでファイル全体)を返すため、一致したものをキャプチャするだけです。

正規表現のコアは、[^>]「シンボル以下」に変換されるものです


0

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 +ii-th 以降のすべての行を出力します
  • grep -n -一致する行の先頭に行番号を追加します
  • head -n1 -最初の行のみを印刷します
  • cut -d : -f 1- :区切り文字として使用して最初の切り取り列を印刷する
  • 2>/dev/null- 式が空を返したtail場合に発生する無音エラー出力$()
  • grep -q- grep終了コードにのみ関心があるため、一致が見つかった場合は黙ってすぐに戻る

誰でも説明できます&>か?私も使用していますが、どこにも文書化されているのを見たことがありません。ところで、なぜ実際にgrepを黙らせる必要があるのですか?grep -qトリックもしませんか?
Syntaxerror

1
&>標準出力と標準エラーの両方をリダイレクトするようにbashに指示します。bashマニュアルのREDIRECTIONを参照してください。良いキャッチのgrep -q ...代わりに私たちも同じようにできるという点であなたはとても正しいですgrep ... &>/dev/null
Emil Lundberg、2014

そう思った。多くの厄介な余分なタイピングの痛みを取り除きます。説明をありがとう-だから私はマニュアルを少しスキップしたに違いない。(少し前にリモートで関連するものを
検索しました

0

これも動作するはずです!

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

$ARGVfile_list /s改行をまたいでモディファイア検索から読み取るときの現在のファイルの名前が含まれます。


0

filepattern *.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

なぜ次のような単純なものではないのですか?

egrep -o 'abc|efg' $file | grep -A1 abc | grep efg | wc -l

0または正の整数を返します。

egrep -o(一致のみを表示、トリック:同じ行に複数の一致がある場合、それらが異なる行にあるかのように複数行の出力を生成します)

  • grep -A1 abc (abcとその後ろの行を出力します)

  • grep efg | wc -l (同じ行または後続の行でabcの後に見つかったefg行の0-nカウント。結果は「if」で使用できます)

  • パターンマッチングが必要な場合は、grepをegrepなどに変更できます。


0

探している2つの文字列「abc」と「efg」の間の距離に関する見積もりがある場合は、次のように使用できます。

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

このようにして、最初のgrepは 'abc'とその後ろに#num1行、その後ろに#num2行を追加した行を返し、2番目のgrepはそれらすべてをふるいにかけて 'efg'を取得します。次に、それらが一緒に表示されるファイルを確認します。


0

ugrep数ヶ月前にリリース:

ugrep 'abc(\n|.)+?efg'

このツールは、速度のために高度に最適化されています。また、GNU / BSD / PCRE-grep互換です。

ファイルの最後まで+?すべての行をefg一緒に一致させたい場合を除いて、レイジー反復を使用する必要があることに注意してくださいefg


-3

これはうまくいくはずです:

cat FILE | egrep 'abc|efg'

一致するものが複数ある場合は、grep -vを使用して除外できます。


2
このコードスニペットは歓迎されており、多少の助けになるかもしれませんが、これが問題を解決する方法理由の説明含めると、大幅に改善されます。あなたが今尋ねている人だけでなく、将来の読者のための質問に答えていることを忘れないでください!回答を編集して説明を追加し、適用される制限と前提を示してください。
Toby Speight 2017年

1
質問で述べたように、それは実際には複数行にわたって検索することはありません。
n.st 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.