正規表現でファイルから複数行を取得するには?
私はしばしば正規表現を介して複数行を取得または修正したいと思います。実例:
XML/SGML ファイルの一部を読み取ろうとしています。形式が正しい構文であるか予測可能な構文である必要はないため、正規表現は適切なパーサーよりも安全です。また、これを完全に実行できることを願っています。いくつかのキーワードのみが知られているシェルスクリプト(SolarisとLinuxで実行されている非構造化ファイル)から。
XMLの例:
<tag1>
<tag2>bar</tag2>
</tag1>
<tag1>
<tag2>foo</tag2>
</tag1>
<tag1>
ここでどこかに含まれているか読んでみたいですfoo
。
このような正規表現は(<tag1>.*?foo.*?</tag1>)
正しい部分を提供する必要がありますが、grep
同じツールはsed
1行でのみ機能します。どうやって入手できますか?
<tag1>
<tag2>foo</tag2>
</tag1>
この場合?
答え1
GNU grepがインストールされている場合-P
(perl-regex)フラグを渡して有効にすることで、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
答え2
#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>$
l
ookを使用していつでもパターンスペースを印刷できます。その後、\n
文字のアドレスを指定できます。
sed l <file
sed
これを処理するために呼び出されるステップが各行に表示されますl
。
\backslash
だから私はテストしましたが、最初の行の後にもう一度必要,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
s?
sed 3<<\SCRIPT
l;s/.*//;h;b}}
その後、再実行します。
_sed_function
#OUTPUT#
\n<tag1>\n <tag2>foo</tag2>\n</tag1>$
いいね!だから私の言葉が正しい。気分が良かった。それではl
インポートしましたが、削除された行をランダムに見てみましょう。現在のアイテムを削除しl
てここに1つを追加すると、!{block}
次のようになります。
!{l;s/.*//;h;b}
_sed_function
#OUTPUT#
\n<tag1>\n <tag2>bar</tag2>\n</tag1>$
殺す前の状況はこんな感じです。
最後にお見せしたいのはH
建築当時の古い空間です。いくつかの重要な概念を紹介したいと思います。そのため、最後のl
ookをもう一度削除して最初の行を変更して、最後に前のスペース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
古い空間生き残ったラインループ - したがって、名前です。それでは、人々はどんな間違いを頻繁に犯しますか?まあ、何ですか?私よく発生する問題は、使用後に削除する必要があることです。この場合はx
一度だけ変更するので、スペースを保ってください。~になるパターン空間とその逆の場合でも、この変化はラインサイクルに耐えることができます。
その結果、私のパターンスペースであった予約済みスペースを削除する必要があります。まず、次を使用して現在のパターンスペースを消去します。
s/.*//
ただ各文字を選択して削除します。d
現在のラインサイクルが終了し、次のコマンドが完了しないため、スクリプトがほとんど中断されるため使用できません。
h
これは同じように機能しますH
が、上書き予約済みスペースなので、予約済みスペースの上に空のパターンスペースをコピーして効果的に削除しました。これで、次のことができます。
b
出て。
これがsed
私がスクリプトを書く方法です。
答え3
@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,$_}
:各行について、その場合は$a
その1
行を配列に追加します@l
。if(/<\/tag1>/)
:現在の行が閉じているタグと一致する場合:if(grep {/foo/} @l){print "@l"}
:配列に含まれる行(@l
との間の行)のいずれかが文字列と一致すると印刷される内容です。<tag1>
</tag1>
foo
@l
$a=0; @l=()
:リスト()を消去して再びゼロに@l=()
設定します。$a
答え4
私の考えに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>