回答:
sed -n '/foo/{:a;N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba};'
sedパターンマッチングでは、/first/,/second/行を1つずつ読み取ります。いくつかの行が/first/それに一致すると、それを記憶し、/second/パターンの最初の一致を待ちます。同時に、そのパターンに指定されたすべてのアクティビティを適用します。その後、プロセスはファイルの終わりまで何度も繰り返されます。
それは私たちが必要とするものではありません。最後に一致した/second/パターンまで調べる必要があります。したがって、最初のエントリだけを探す構造を構築します/foo/。見つかると、サイクルaが始まります。で一致バッファに新しい行を追加し、Nそれがパターンに一致するかどうかを確認します/bar/。もしそうなら、それを出力してマッチバッファをクリアし、ジャニーウェイはでサイクルの始めにジャンプしbaます。
また、でバッファをクリーンアップした後、改行記号を削除する必要があります/^\n/s/^\n//。より良い解決策があると確信していますが、残念ながらそれは思いつかなかったのです。
すべてが明確であることを願っています。
sedバージョン(BSD sed(Macにあるものなど)など)では、タグの後に改行または文字列の末尾を続ける必要があるため、次の微調整が必要です。 sed -n -e '/foo/{:a' -e 'N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba' -e '};' これはGNU sed でも機能するため、この変更(複数の-e引数sedでブランチを使用する場合は、各ブランチ名の後にargを付けることをお勧めします。
私は小さなPerlワンライナーでそれをします。
cat <<EOF | perl -ne 'BEGIN { $/ = undef; } print $1 if(/(foo.*bar)/s)'
A line
like
foo
this
foo
bar
something
something else
foo
bar
and
the
rest
EOF
収量
foo
this
foo
bar
something
something else
foo
bar
Eのではなく、eそして-00777代わりの$/ビット((1)perlrunを参照します)。これは、次のように短くなりperl -0777 -nE 'say /(foo.*bar)/s'ます。
-0[octal]私のワークフローでは特にそうだと私は確信しています!それをありがとう
以下は、多くのメモリを必要としない2パスのGNU sedソリューションです。
< infile \
| sed -n '/foo/ { =; :a; z; N; /bar/=; ba }' \
| sed -n '1p; $p' \
| tr '\n' ' ' \
| sed 's/ /,/; s/ /p/' \
| sed -n -f - infile
sed呼び出しはinfileを渡し、の最初の出現fooとその後のすべての出現を見つけbarます。sed2つの呼び出しとスクリプトsedと1 tr。第三の出力がsedある[start_address],[end_address]p括弧なし、。sed通過しinfile、見つかったアドレスとその間のすべてを出力します。入力ファイルがメモリに快適に収まる場合は、単純にしてください。
入力ファイルが大きい場合は、を使用csplitして最初fooとそれ以降のすべての部分で分割してから、それらbarを組み立てることができます。作品が呼び出されpiece-000000000、piece-000000001(ここでは、プレフィックス選択など、piece-他の既存のファイルと衝突しません)。
csplit -f piece- -n 9 - '%foo%' '/bar/' '{*}' <input-file
(Linux以外のシステムでは、中かっこ内で大きな数を使用する必要があります(例:)、オプション{999999999}を渡す-k。その数はbarピースの数です。)
ですべての部品を組み立てることができますがcat piece-*、これにより、最初の部品からすべてが得られますfoo。最初の最後の部分を削除してください。によって生成されるファイル名にcsplitは特殊文字が含まれていないため、特別な引用の予防策を講じなくても、それらを処理できます。
rm $(echo piece-* | sed 's/.* //')
または同等に
rm $(ls piece-* | tail -n 1)
これで、すべてのピースを結合して一時ファイルを削除できます。
cat piece-* >output
rm piece-*
ディスクスペースを節約するために連結されている部分を削除する場合は、ループで実行します。
mv piece-000000000 output
for x in piece-?????????; do
cat "$x" >>output; rm "$x"
done
ここに別の方法がありsedます:
sed '/foo/,$!d;H;/bar/!d;s/.*//;x;s/\n//' infile
/foo/,$範囲内の各行(!この範囲外の行はd削除されます)をH古いスペースに追加します。一致しない行はbar削除されます。一致する行では、パターンスペースが空になり、x保留スペースで変更され、パターンスペースの先頭の空行が削除されます。
入力が膨大で、barこれがほとんど発生しない場合は、各行をパターンスペースに引き込んでから、毎回、パターンスペースを確認するよりも(はるかに)高速ですbar。
説明:
sed '/foo/,$!d # delete line if not in this range
H # append to hold space
/bar/!d # if it doesn't match bar, delete
s/.*// # otherwise empty pattern space and
x # exchange hold buffer w. pattern space then
s/\n// # remove the leading newline
' infile
もちろん、これがファイル(メモリに収まる)の場合は、次のコマンドを実行するだけです。
ed -s infile<<'IN'
.t.
/foo/,?bar?p
q
IN
前方と後方ed を検索できるからです。
シェルがプロセス置換をサポートしている場合は、コマンド出力をテキストバッファーに読み込むこともできます。
printf '%s\n' .t. /foo/,?bar?p q | ed -s <(your command)
またはそれはと、しない場合gnu ed:
printf '%s\n' .t. /foo/,?bar?p q | ed -s '!your command'
Grepもそれを行うことができます(まあ、GNU grep):
<infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'
<infile grep -ozP ' # call grep to print only the matching section (`-o`)
# use NUL for delimiter (`-z`) (read the whole file).
# And using pcre regex.
(?s)foo.*bar # Allow the dot (`.`) to also match newlines.
' | tr '\0' '\n' # Restore the NULs to newlines.
質問の本文からの入力:
$ <infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'
foo
this
foo
bar
something
something else
foo
bar
fooと最後barを見つけて、その間にあるものがあれば、すべてを印刷します。ストリームでは、最初のまで読み取りfoo、後続のすべての行をEOFまでメモリにバッファリングして、abarが表示されるたびにバッファをフラッシュする必要があります。これは、ストリーム全体をメモリにバッファリングすることを意味します。