sed正規表現は、パターンを含む段落全体をキャプチャできません。

sed正規表現は、パターンを含む段落全体をキャプチャできません。

このXMLファイルがあります(例)。

<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2/>


<This is a line of text with a year=33020 month=12 in it
This line of text does not have a year or month in it
This year=33020 is the current year the current month=1
This is the year=33020 the month=2/>

私のLinuxディストリビューション(sed(GNU sed)4.2.2)に付属のインストールを使用して、次の正規sed表現を使用してこのファイルを検索します。

 sed -En 'N;s/\<(This.*2020.*[\s\S\n]*?)\>/\1/gp' test2.txt

ただし、次の文字列のみをキャプチャします。

<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it

しかし、まずは全体を捉えようとします。パターン間<と埋め込み。>

私がここで何を間違っているのか?

答え1

これが期待どおりに機能しない理由は、正規表現<>エスケープする必要がなく、特別な意味がないからです。しかし、\<そして\> するGNU拡張正規表現(有効化を含む-E)には特別な意味があります。つまり、単語の境界で一致します。単語の\<始まりと終わりを一致させます。\>したがって、\<(This実際には一致しませんが、<単語の先頭に一致しますThis\>最後のものも同じだ。 GNUsedマニュアルには一例これはほぼ正確にあなたが追求するものです:

$ sed -En '/./{H;1h;$!d} ; x; s/(<This.*2020.*?>)/\1/p;' file
<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2/>

sedこの種の作業には特に適していないと思います。私は以下を使用しますperl

$ perl -000 -ne 'chomp;/<.*2020.*?>/s && print "$_\n"; exit' file
<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2/>

ここでは、「短絡モード」()でPerlを使用しています。-000これは、「行」が2つの連続した\n文字(つまり空白行)として定義されることを意味します。このスクリプトは次のことを行います。

  • chomp:「行」(段落)の末尾にある末尾の改行を削除します。
  • /<.*2020.*?>/s && print "$_\n":この「行」(段落)が<0個以上の文字、20200個以上の文字、0個以上の文字と一致する場合、>改行文字(print "$_\n")が追加されて印刷されます。s一致演算子の修飾子は.改行文字の一致を許可します。

別のオプションは次のとおりですawk

$ awk 'BEGIN{RS="\n\n"} /<.*2020.+?>/' file
<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2/>

レコード区切り文字をRS2つの連続した改行に設定し、上記と同じ正規表現を使用して一致させます。一致が見つかった場合(または他のジョブがtrueを返すとき)、デフォルトの動作は現在のレコードを印刷するため、awk必要な内容が印刷されます。

答え2

まず、ほとんどのテキスト処理ツール(またはsedawkは1行ずつ作業するため、段落全体を一致させるには少し余分な作業が必要です。これは可能ですが、予期しない出力が表示される理由の1つです。

次に、XMLタグ区切り文字のため、入力は構造化テキストのように見えます。したがって、xmlstarletこれを処理するには、他の専門ツールを使用するのが最善です。 (修正する:今、あなたのコメントでこれを確認したので、xmlstarletまたは同様のツールを使用することをお勧めします。 )

つまり、テキストが例に似ていて、インストールで複数文字のレコード区切り文字awk(GNU Awkなど)を許可する場合は、次のプログラムが機能するはずです。

awk -v RS="<|/>" '/2020/' input.txt

変数RSに複数の文字が含まれている場合は正規表現として解釈されるため、a<またはaは/>デフォルトではなく「レコード区切り文字」と見なされます\n。したがって、一致条件は個々の行だけでなく、これらのタグ間の全文にも適用されます。

結果:

This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2

「タグを開く」<と「タグを閉じる」/>文字の組み合わせは、レコード区切り文字として選択されているため、出力から削除されます。一方、これは、「段落」が空行で区切られていなくても機能することを意味します。 (ただし、そのタグの外側にパターンと一致する「ストレス」テキストがある場合、そのテキストも一致します。)

探している正規表現を/ ... /プログラムの一部に配置できます(sedアドレスステートメントのように)。ただし、固定文字列を探している場合は、次のことをお勧めします。

awk -v RS="<|/>" 'index($0,"2020")' input.txt

代わりに。

答え3

正しい形式の XML 文書が次のとおりであるとします。

<root>
<thing  year="2019"
        month="1"
        day="1" />
<thing  year="2020"
        month="5"
        day="13" />
<thing  year="2021"
        month="7"
        day="3" />
</root>

次のコマンドを使用して、thing属性に値を持つ各ノードのコピーを抽出できます。2020yearxmlstarlet

$ xmlstarlet sel -t -c '//thing[@year = "2020"]' -nl file
<thing year="2020" month="5" day="13"/>

ノードの内部とその属性の間のスペースは、ドキュメントの内容とは何の関係もありません。

答え4

Raku(以前のPerl_6)の使用

このスレッドの他の答えからインスピレーションを得た2つの答えは次のとおりです。最初の答えは@terdonと@AdminBeeに触発された段落で区切られ、grepsは正しい年を表します。

raku -e 'slurp.split("\n\n").grep(/2020/).put;' 

結果:

<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2/>

Larry Wallによれば、Rakuは言語内でより多くの作業を簡単に実行する機能を提供し、特別なコマンドラインスイッチへの依存を減らします。 「ヒント#2」を参照してください。

https://www.nntp.perl.org/group/perl.perl6.users/2020/07/msg9004.html

2番目の方法はRakuのルーチンを使用することですcomb。正規表現「マッチャー」を使用して、テキストを一致以外の要素に分割します(追加処理に役立ちます)。 Rakuのドキュメントでは、次のように説明しますcomb$matcher「最も重複しない一致を検索して返します。」$inputSeq$limit

raku -e '.put for slurp.comb(/^^ "<This" .*? "/>" $$ / ).grep(/2020/);' 

結果:

<This is a line of text with a year=2020 month=12 in it
This line of text does not have a year or month in it
This year=2021 is the current year the current month=1
This is the year=2021 the month=2/>

上記のコードは^^ 行の始まり主張とその後$$ 行末パラドックス。デフォルトでは、.ドットワイルドカードはRakuの空白(改行を含む)と一致するため、上記はcombテキストを複数行ブロック(要素)に分割できます。

明らかに、実際のXML文書の最も満足のいく結果は、コミュニティサポートモジュールを含むRakuなどの専用ツールおよびXML/またはライブラリを使用することです。XML

https://github.com/raku-community-modules/XML
https://raku.org/

関連情報