複数の行にわたってパターンを「grep」するにはどうすればよいですか?


24

grep/ を誤用しているようですegrep

私は複数行の文字列を検索しようとしていて、探しているものが一致するはずだとわかっている間に一致を見つけることができませんでした。元々、自分の正規表現は間違っていると思っていましたが、最終的にはこれらのツールが行ごとに動作することを読みました(また、私の正規表現は非常に些細なため問題になりません)。

それでは、複数の行にわたってパターンを検索するためにどのツールを使用しますか?



1
@CiroSantilli-このQとあなたがリンクしたQは重複しているとは思いません。もう1つのQは、複数行のパターンマッチをどのように行うか(つまり、これを行うためにどのツールを使用するか、使用できますか)を尋ねていますgrep。それらは密接に関連していますが、重複ではありません、IMO。
slm

@simこれらのケースを決定するのは難しいです:あなたの主張を見ることができます。ユーザー"grep"が動詞「to grep」を提案すると言ったので、この特定のケースは複製としてはより良いと思います。受け入れられたものを含むトップアンサーはgrepを使用しません。
Ciro Santilli新疆改造中心法轮功六四事件14

回答:


24

以下に、複数の行にまたがるような動作をsed提供するものを示しますgrep

sed -n '/foo/{:start /bar/!{N;b start};/your_regex/p}' your_file

使い方

  • -n すべての行を印刷するデフォルトの動作を抑制します
  • /foo/{}foo一致する行に波線の内側にあるものを照合して実行するように指示します。fooパターンの開始部分に置き換えます。
  • :start は、正規表現の終わりが見つかるまでループを維持するための分岐ラベルです。
  • /bar/!{}一致しない行への波線の内容を実行しますbarbarパターンの最後の部分に置き換えます。
  • Nアクティブなバッファに次の行を追加しsedます(これをパターンスペースと呼びます)
  • b startstartパターンスペースにが含まれていない限り、次の行を追加し続けるために、前に作成したラベルに無条件に分岐しますbar
  • /your_regex/p一致する場合、パターンスペースを出力しますyour_regexyour_regex複数行にわたって一致させたい表現全体で置き換える必要があります。

1
+1これをtooliktに追加!ありがとう。
wmorrison365

注:MacOSでは、これは次のようになりますsed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
スタンジェームズ

1
sed: unterminated {エラーの取得
ノーマッド

ここでは暗闇で@Nomaed Shotを使用していますが、正規表現に「{」文字が含まれていますか?その場合は、バックスラッシュでエスケープする必要があります。
ジョセフR.

1
@Nomaed 実装間の違いに関係しているようですsed。上記のスクリプトを標準に準拠させるために、その回答の推奨事項に従うことを試みましたが、「開始」は未定義のラベルであると教えられました。したがって、これが標準に準拠した方法で実行できるかどうかはわかりません。あなたがそれを管理する場合、私の答えを編集してください。
ジョセフR.

19

通常、と呼ばれるツールを使用します。pcregrepこれは、yumまたはを使用してほとんどのLinuxフレーバーにインストールできますapt

例えば

testfileコンテンツで名前が付けられたファイルがある場合

abc blah
blah blah
def blah
blah blah

次のコマンドを実行できます。

$ pcregrep -M  'abc.*(\n|.)*def' testfile

複数行にわたってパターンマッチングを実行します。

さらに、同じことを行うこともできますsed

$ sed -e '/abc/,/def/!d' testfile

5

Perlを使用したより簡単なアプローチを次に示します。

perl -e '$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m' file

または(JosephR sedルートを取ったので、私は恥知らずに彼の提案を盗みます)

perl -n000e 'print $& while /^foo.*\nbar.*\n/mg' file

説明

$f=join("",<>);:これはファイル全体を読み取り、その内容(改行とすべて)を変数に保存します$f。次に、一致を試行し、一致foo\nbar.*\nする場合に出力します(特殊変数$&は最後に見つかった一致を保持します)。の///mは、改行全体で正規表現を一致させるために必要です。

-0入力レコードセパレータを設定します。これを設定00すると、「段落モード」が有効になり、Perlは連続した改行(\n\n)をレコード区切り文字として使用します。連続する改行がない場合、ファイル全体が一度に読み込まれます(丸lurみされます)。

警告:

ではない大きなファイルのためにこれを行うには、ファイル全体をメモリにロードしますし、それが問題になることがあります。


2

これを行う1つの方法は、Perlを使用することです。たとえば、次の名前のファイルの内容はfoo次のとおりです。

foo line 1
bar line 2
foo
foo
foo line 5
foo
bar line 6

さて、fooで始まり、その後にbarで始まる行が続く行と一致するPerlがあります。

cat foo | perl -e 'while(<>){$all .= $_}
  while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) {
  print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m;
}'

分解されたPerl:

  • while(<>){$all .= $_} これにより、標準入力全体が変数にロードされます $all
  • while($all =~ 変数 allには正規表現がありますが...
  • /^(foo[^\n]*\nbar[^\n]*\n)/m 正規表現:行の先頭にfooがあり、その後に任意の数の非改行文字が続き、改行が続き、すぐに "bar"が続き、残りの行にはbarが含まれます。 /m正規表現の最後は「複数行にわたる一致」を意味します
  • print $1 括弧内にあった正規表現の部分(この場合、正規表現全体)を出力します
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m 正規表現の最初の一致を消去して、問題のファイル内の正規表現の複数のケースと一致できるようにします

そして出力:

foo line 1
bar line 2
foo
bar line 6

3
:ちょうどあなたのPerlは、より慣用的に短縮することが可能と言うことでドロップperl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
ジョセフ・R.

2

grep alternative siftは複数行のマッチングをサポートしています(免責事項:私は著者です)。

次のものtestfileが含まれているとします:

<本>
  <title> Lorem Ipsum </ title>
  <説明> Lorem ipsum dolor sit amet、consectetur
  adipiscing elit、sed do eiusmod tempor incididunt ut
  Labore et dolore magna aliqua </ description>
</ book>


sift -m '<description>.*?</description>' (説明を含む行を表示します)

結果:

テストファイル:<説明> Lorem ipsum dolor sit amet、consectetur
テストファイル:アドリブのエリート、sed do eiusmod tempor incididunt ut
テストファイル:Labore et dolore magna aliqua </ description>


sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (説明を抽出して再フォーマットする)

結果:

description = "Lorem ipsum dolor sit amet、consectetur
  adipiscing elit、sed do eiusmod tempor incididunt ut
  Labore et dolore magna aliqua」

1
非常に素晴らしいツール。おめでとうございます!Ubuntuのようなディストリビューションに含めるようにしてください。
ロウレンソ

2

Perl-regexpパラメータをサポートする通常のgrep Pがこのジョブを実行します。

$ echo 'abc blah
blah blah
def blah
blah blah' | grep -oPz  '(?s)abc.*?def'
abc blah
blah blah
def

(?s) DOTALL修飾子と呼ばれ、正規表現にドットを作成して、文字だけでなく改行にも一致させます。


私はこのソリューションをしようとすると、出力は「DEF」で終了しますが、ファイル「何とか」の端部に行くしない
バックリー

多分あなたのgrepは-Pオプションをサポートしていません
Avinash Raj

1

grepと-Aオプションを別のgrepで使用して、これを解決しました。

grep first_line_word -A 1 testfile | grep second_line_word

-A 1オプションは、見つかった行の1行後に印刷します。もちろん、ファイルと単語の組み合わせに依存します。しかし、私にとっては、最速かつ信頼できるソリューションでした。


エイリアスgrepp = 'grep --color = auto -B10 -A20 -i'その後cat somefile | grepp blah | grepp foo | greppバー...はい、それら-Aおよび-Bは、非常に便利です...あなたが最良の答え持っている
スコットStensland

1

次を含むファイルtest.txtがあるとします:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

次のコードを使用できます。

sed -n '/foo/,/bar/p' test.txt

次の出力の場合:

foo
here
is the
text
to keep between the 2 patterns
bar

1

自分自身を除く2つのパターンの間のテキストを取得する場合。

次を含むファイルtest.txtがあるとします:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

次のコードを使用できます。

 sed -n '/foo/{
 n
 b gotoloop
 :loop
 N
 :gotoloop
 /bar/!{
 h
 b loop
 }
 /bar/{
 g
 p
 }
 }' test.txt

次の出力の場合:

here
is the
text
to keep between the 2 patterns

それがどのように機能するか、ステップバイステップでやってみましょう

  1. /foo/{ 行に「foo」が含まれる場合にトリガーされます
  2. n パターンスペースを次の行、つまり「here」という単語で置き換えます
  3. b gotoloop ラベル「gotoloop」への分岐
  4. :gotoloop ラベル「gotoloop」を定義します
  5. /bar/!{ パターンに「バー」が含まれていない場合
  6. h ホールドスペースをパターンに置き換え、「here」がホールドスペースに保存されるようにします
  7. b loop ラベル「loop」への分岐
  8. :loop ラベル「ループ」を定義します
  9. N ホールドスペースにパターンを追加します。
    保留スペースには次が含まれます:
    "here"
    "is the"
  10. :gotoloop これでステップ4になり、行に「bar」が含まれるまでループします
  11. /bar/ ループが終了し、「バー」が見つかった、それがパターンスペース
  12. g パターンスペースは、メインループ中に保存された「foo」と「bar」の間のすべての行を含むホールドスペースに置き換えられます
  13. p パターンスペースを標準出力にコピーする

できた!


よくできました、+ 1。私は通常、改行をSOHに入れ、通常のsedコマンドを実行してから改行を置き換えることで、これらのコマンドの使用を避けています。
A.Danischewski
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.