パターンの最初と最後の出現の間のすべての行を取得するにはどうすればよいですか?


8

最初に出現するパターンfooから最後に出現するパターンまでの範囲の行のみを取得するように、ファイル(適切な入力ストリーム)をトリミングするにはどうすればよいbarですか?

たとえば、次の入力を考えます。

A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest

私はこの出力を期待します:

foo
this 
foo
bar
something
something else
foo
bar

3
シングルパスストリームまたはファイル?ランダムアクセスが許可されている場合、これははるかに簡単です。ファイルを使用すると、最初fooと最後barを見つけて、その間にあるものがあれば、すべてを印刷します。ストリームでは、最初のまで読み取りfoo、後続のすべての行をEOFまでメモリにバッファリングして、a barが表示されるたびにバッファをフラッシュする必要があります。これは、ストリーム全体をメモリにバッファリングすることを意味します。
jw013

回答:


6
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//。より良い解決策があると確信していますが、残念ながらそれは思いつかなかったのです。

すべてが明確であることを願っています。


1
できます!あなたがそのようなコマンドの構築を私たちに説明してくれるなら、それはとてもクールです。いくつかのWebサイトからオンラインでコピー/貼り付けするだけで、ばかげていると思います;)
rahmu

1
申し訳ありませんが、説明付きの回答は投稿していません。今それはポストにあります。
ラッシュ

一部のsedバージョン(BSD sed(Macにあるものなど)など)では、タグの後に改行または文字列の末尾を続ける必要があるため、次の微調整が必​​要です。 sed -n -e '/foo/{:a' -e 'N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba' -e '};' これはGNU sed でも機能するため、この変更(複数の-e引数sedでブランチを使用する場合は、各ブランチ名の後にargを付けることをお勧めします。
ワイルドカード2015年

4

私は小さな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

3
これは、コードゴルフた場合は、使用できるEのではなく、eそして-00777代わりの$/ビット((1)perlrunを参照します)。これは、次のように短くなりperl -0777 -nE 'say /(foo.*bar)/s'ます。
トール

1
私はこれらのフラグについて知りませんでした!-0[octal]私のワークフローでは特にそうだと私は確信しています!それをありがとう
user1146332

3

以下は、多くのメモリを必要としない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、見つかったアドレスとその間のすべてを出力します。

2

入力ファイルがメモリに快適に収まる場合は、単純にしてください

入力ファイルが大きい場合は、を使用csplitして最初fooとそれ以降のすべての部分で分割してから、それらbarを組み立てることができます。作品が呼び出されpiece-000000000piece-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

1

ここに別の方法があり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'

0

任意のUNIXシステムの任意のシェルで任意のawkを使用し、ファイル全体または入力ストリームを一度にメモリに読み込まずに:

$ awk '
    f {
        rec = rec $0 ORS
        if (/bar/) {
            printf "%s", rec
            rec = ""
        }
        next
    }
    /foo/ { f=1; rec=$0 ORS }
' file
foo
this
foo
bar
something
something else
foo
bar

0

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.