正規表現でファイルから複数の行を取得する方法は?


10

正規表現でファイルから複数の行を取得する方法は?

正規表現で複数の行を取得したり、複数の行を変更したりすることがよくあります。事例:

私はXML / SGMLファイルの一部を読み込もうとしています(形式が正しくないか、予測可能な構文であるとは限らないため、正規表現は適切なパーサーよりも安全です。さらに、これを完全に実行できるようにしたいと思います。いくつかのキーワードだけがわかっている非構造化ファイル)(SolarisおよびLinuxで実行される)シェルスクリプト内。

XMLの例:

<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>

これから私は<tag1>それがその中にfooどこかに含まれている場合に読みたいと思います。

以下のような正規表現は、(<tag1>.*?foo.*?</tag1>)右側の部分が、のようなツール与えるべきであるgrepsed、単一のラインを持つ私のためだけの仕事を。どうすれば入手することができますか

<tag1>
 <tag2>foo</tag2>
</tag1>

この例では?



@evilsoupそれは本当ですが、私の質問は特にXML / SGMLファイルについてではなく、テキストファイルについてです。
デン

回答:


7

あなたはGNU grepのがインストールされている場合は、に渡すことで、複数行の検索を行うことができます-P(perlの正規表現で)フラグと活性化PCRE_DOTALL(?s)

grep -oP '(?s)<tag1>(?:(?!tag1).)*?foo(?:(?!tag1).)*?</tag1>' file.txt
<tag1>
<tag2>foo</tag2>
</tag1>

上記がプラットフォームで機能しない場合は-z、さらにフラグを渡してみてください。これにより、grepはNULを行区切り文字として扱い、ファイル全体が1行のように見えます。

grep -ozP '(?s)<tag1>(?:(?!tag1).)*?foo(?:(?!tag1).)*?</tag1>' file.txt

これは、OPのサンプルファイルで実行すると、システムに出力がありません。
terdon

私のために働く。+1。(?s)ヒントをありがとう
Nathan Wallace

@terdon、実行しているGNU grepのバージョンは?
iruvar 2013年

(GNU grep) 2.14Debianの@ 1_CR 。OPの例をそのまま(最後の改行のみを追加して)コピーして実行grepしましたが、結果がありませんでした。
terdon

1
@slm、私はRHELのpcre 6.6、GNU grep 2.5.1を使用しています。あなたのプラットフォームgrep -ozPgrep -oPはなく試してみませんか?
iruvar 2013年

3
#begin command block
#append all lines between two addresses to hold space 
    sed -n -f - <<\SCRIPT file.xml
        \|<tag1>|,\|</tag1>|{ H 
#at last line of search block exchange hold and pattern space 
            \|</tag1>|{ x
#if not conditional ;  clear buffer ; branch to script end
                \|<tag2>[^<]*foo[^\n]*</tag2>|!{s/.*//;h;b}
#do work ; print result; clear buffer ; close blocks
    s?*?*?;p;s/.*//;h;b}}
SCRIPT

上記を行う場合、表示するデータを考慮して、その最後のクリーンアップ行の前に、sed次のようなパターンスペースで作業する必要があります。

 ^\n<tag1>\n<tag2>foo</tag2>\n</tag1>$

lookを使用すると、いつでもパターンスペースを印刷できます。その後、\nキャラクターをアドレス指定できます。

sed l <file

呼び出さsedれた段階で各行が処理することを示しますl

だから私はそれをテストしたところ、最初の行の\backslash後にもう1つ必要です,commaが、それ以外はそのまま動作します。ここでそれをに入れて、_sed_functionこの回答全体でデモンストレーションの目的で簡単に呼び出すことができるようにします(コメントを含めて機能しますが、簡潔にするためにここでは削除されています)。

_sed_function() { sed -n -f /dev/fd/3 
} 3<<\SCRIPT <<\FILE 
    \|<tag1>|,\|</tag1>|{ H
        \|</tag1>|{ x
            \|<tag2>[^<]*foo[^\n]*</tag2>|!{s/.*//;h;b}
    s?*?*?;p;s/.*//;h;b}}
#END
SCRIPT
<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>
FILE


_sed_function
#OUTPUT#
<tag1>
 <tag2>foo</tag2>
</tag1>

では、pをに切り替えてl、スクリプトの開発中に何を処理しているかを確認し、non-opデモを削除しs?て、最後の行がsed 3<<\SCRIPT次のようになるようにします。

l;s/.*//;h;b}}

次に、もう一度実行します。

_sed_function
#OUTPUT#
\n<tag1>\n <tag2>foo</tag2>\n</tag1>$

OK!だから私は正しかった-それは良い感じです。それでは、lフックを入れ替えて削除する行を見てみましょう。電流lを削除して1をに追加して、!{block}次のようにします。

!{l;s/.*//;h;b}

_sed_function
#OUTPUT#
\n<tag1>\n <tag2>bar</tag2>\n</tag1>$

完全に消去する直前の状態です。

最後にもう1つお見せしたいのは、H私たちが構築する古いスペースです。いくつかの重要な概念について説明します。したがって、最後のlフックを再度削除し、最初の行を変更して、最後にH古いスペースをのぞくように追加します。

{ H ; x ; l ; x

_sed_function
#OUTPUT#
\n<tag1>$
\n<tag1>\n <tag2>bar</tag2>$
\n<tag1>\n <tag2>bar</tag2>\n</tag1>$
\n<tag1>$
\n<tag1>\n <tag2>foo</tag2>$
\n<tag1>\n <tag2>foo</tag2>\n</tag1>$

H古いスペースラインサイクルを生き延びます-したがって、その名前。それで、人々がよくつまずくのは-わかりました、がつまずくのは-それを使った後で削除する必要があるということです。IのみEこの場合にx変更すると、保持空間が非常になるパターンスペースおよびその逆、この変化はまた、ラインサイクルに耐えます。

その結果、以前はパターンスペースであったホールドスペースを削除する必要があります。まず、現在のパターンスペースを次のようにクリアします。

s/.*//

これは単にすべての文字を選択して削除するだけです。d現在の行サイクルが終了し、次のコマンドが完了せず、スクリプトがかなり無駄になるため、使用できません。

h

同様にこれは作品Hが、それは上書きされ、私はちょうどそれを効果的に削除する、私のホールドスペースの上に私の空白のパターンスペースをコピーしたので、ホールドスペースを。今私はちょうどできます:

b

アウト。

そして、それが私がsedスクリプトを書く方法です。


@slmに感謝!あなたは本当に大丈夫です、あなたはそれを知っていますか?
mikeserv 2014年

おかげで、素晴らしい仕事、3kへの非常に迅速な上昇、次の5k 8
slm

わからない、@ slm。私はここで次第に学習を減じているのを見始めています-多分私はその有用性を超えています。私はそれについて考えなければならない。ほんの数週間でサイトにやってくることすらありません。
mikeserv 2014年

少なくとも10kに到達します。ロックを解除する価値があるものはすべてそのレベルにあります。欠けないようにしてください。5kはかなり速くなります。
slm

1
まあ、@ slm-とにかく珍しい品種です。ただし、複数の回答については同意します。それがいくつかのqsが閉じられるときに私を悩ませる理由です。しかし、それが実際に起こることはめったにありません。ありがとう、slm。
mikeserv 2014年

2

@jamespfinnの答えは、ファイルが例のように単純であれば、完全にうまく機能します。<tag1>2行を超える可能性のあるより複雑な状況がある場合は、もう少し複雑なトリックが必要になります。例えば:

$ cat foo.xml
<tag1>
 <tag2>bar</tag2>
 <tag3>baz</tag3>
</tag1>
<tag1>

 <tag2>foo</tag2>
</tag1>

<tag1>
 <tag2>bar</tag2>

 <tag2>foo</tag2>
 <tag3>baz</tag3>
</tag1>
$ perl -ne 'if(/<tag1>/){$a=1;} 
            if($a==1){push @l,$_}
            if(/<\/tag1>/){
              if(grep {/foo/} @l){print "@l";}
               $a=0; @l=()
            }' foo.xml
<tag1>

  <tag2>foo</tag2>
 </tag1>
<tag1>
  <tag2>bar</tag2>

  <tag2>foo</tag2>
  <tag3>baz</tag3>
 </tag1>

perlスクリプトは、入力ファイルの各行を処理し、

  • if(/<tag1>/){$a=1;}:開始タグ()が見つかった場合、変数$aが設定されます。1<tag1>

  • if($a==1){push @l,$_}:各ラインのため、場合$a1、配列にその行を追加します@l

  • if(/<\/tag1>/) :現在の行が終了タグと一致する場合:

    • if(grep {/foo/} @l){print "@l"}:配列に保存されている行@l(これらの行との間の行)が文字列<tag1></tag1>一致するfoo場合、の内容を出力し@lます。
    • $a=0; @l=():リストを空にして(@l=())、$a0に戻します。

これは、「foo」を含む複数の<tag1>がある場合を除き、うまく機能します。その場合、最初の<tag1>の始まりから最後の</ tag1>の終わりまですべてのものを出力します...
Den

私は3が含まれている私の答えに示す例でそれをテストし@den <tag1>fooし、それが正常に動作します。いつ失敗しますか?
terdon

正規表現を使用してXMLを解析するのはとても間違っているように感じます:)
Braiam

1

ここにsed代替があります:

sed -n '/<tag1/{:x N;/<\/tag1/!b x};/foo/p' your_file

説明

  • -n 指示がない限り、行を印刷しないことを意味します。
  • /<tag1/ 最初に開始タグに一致します
  • :x 後でこのポイントにジャンプできるようにするラベルです
  • N 次の行をパターンスペース(アクティブバッファー)に追加します。
  • /<\/tag1/!b x現在のパターンスペースに終了タグが含まれていない場合は、x前に作成したラベルに分岐します。したがって、終了タグが見つかるまで、パターンスペースに線を追加し続けます。
  • /foo/pは、現在のパターンスペースがと一致する場合fooに印刷されることを意味します。

1

たとえば、既知の終了タグの場合、終了タグをレコード区切り記号として扱うことで、GNU awkでそれを行うことができます</tag1>

gawk -vRS="\n</tag1>\n" '/foo/ {printf "%s%s", $0, RT}'

またはより一般的に(終了タグの正規表現を使用)

gawk -vRS="\n</[^>]*>\n" '/foo/ {printf "%s%s", $0, RT}'

@terdonでテストするfoo.xml

$ gawk -vRS="\n</[^>]*>\n" '/foo/ {printf "%s%s", $0, RT}' foo.xml
<tag1>

 <tag2>foo</tag2>
</tag1>

<tag1>
 <tag2>bar</tag2>

 <tag2>foo</tag2>
 <tag3>baz</tag3>
</tag1>

0

ファイルが上記に示したとおりに構造化されている場合は、grepに-A(後の行)および-B(前の行)フラグを使用できます。次に例を示します。

$ cat yourFile.txt 
<tag1>
 <tag2>bar</tag2>
</tag1>
<tag1>
 <tag2>foo</tag2>
</tag1>
$ grep -A1 -B1 bar yourFile.txt 
<tag1>
 <tag2>bar</tag2>
</tag1>
$ grep -A1 -B1 foo yourFile.txt 
<tag1>
 <tag2>foo</tag2>
</tag1>

のバージョンがgrepサポートしている場合は-C、周囲のN行を出力する、より単純な(コンテキストの)オプションを使用することもできます。

$ grep -C 1 bar yourFile.txt 
<tag1>
 <tag2>bar</tag2>
</tag1>

ありがとうございます。これは単なる例であり、実際のものはかなり予測不可能に見えます;-)
Den

1
それはfooを含むタグを見つけるのではなく、単にfooを見つけてコンテキストの行を表示するだけです
Nathan Wallace

@NathanWallaceはい、これはまさにOPが求めていたものです。この回答は、質問で与えられたケースでは完全にうまく機能します。
terdon

@terdonそれは質問がすることとはまったく違います。引用:「<tag1>内にfooが含まれている場合、<tag1>を読みたい」この解決策は、「 'foo'がどこにあるかに関係なく、 'foo'と1行のコンテキストを読みたい」のようなものです。あなたの論理に従って、この質問への等しく有効な答えはでしょうtail -3 input_file.xml。はい、この特定の例では機能しますが、質問に対する有用な回答ではありません。
Nathan Wallace

@NathanWallace私のポイントは、OPがこれは有効なXML形式ではないことを明確に述べていることでした。その場合、OPが検索している文字列の周りにN行を出力するだけで十分でした。利用できる情報があれば、この答えは十分まともでした。
terdon
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.