POSIX.2の最後のマーカーからEOFへのテキストの取得


8

次のようなマーカー行のあるテキストがあります。

aaa
---
bbb
---
ccc

最後のマーカー(包括的ではない)からEOFまでのテキストを取得する必要があります。この場合、それは

ccc

POSIX.2内にエレガントな方法はありますか?最初:今、私は二つの実験に使用nlし、grepそれぞれの行番号の最後に出現するために。次に、行番号sedを抽出し、問題のチャンクを抽出するために使用します。

テキストセグメントは非常に大きくなる可能性があるため、テキストをバッファーに追加するようなテキスト追加メソッドを使用することを恐れています。マーカーに遭遇した場合、バッファーを空にし、EOFで最後のチャンクをバッファ。

回答:


6

セグメントが本当に巨大でない限り(おそらく、これは大きなRAMを節約できません。これはおそらく、これが大きなファイルシステムを制御する小さな組み込みシステムであるためです)、シングルパスが本当に優れたアプローチです。高速になるだけでなく、最も重要なこととして、ソースをストリームにできるため、そこから読み取られて保存されていないデータが失われます。これは本当にawkの仕事ですが、sedでもできます。

sed -n -e 's/^---$//' -e 't a' \
       -e 'H' -e '$g' -e '$s/^\n//' -e '$p' -e 'b' \
       -e ':a' -e 'h'              # you are not expected to understand this
awk '{if (/^---$/) {chunk=""}      # separator ==> start new chunk
      else {chunk=chunk $0 RS}}    # append line to chunk
     END {printf "%s", chunk}'     # print last chunk (without adding a newline)

2パスアプローチを使用する必要がある場合は、最後のセパレーターの行オフセットを決定し、そこから印刷します。または、バイトオフセットを決定し、そこから出力します。

</input/file tail -n +$((1 + $(</input/file         # print the last N lines, where N=…
                               grep -n -e '---' |   # list separator line numbers
                               tail -n 1 |          # take the last one
                               cut -d ':' -f 1) ))  # retain only line number
</input/file tail -n +$(</input/file awk '/^---$/ {n=NR+1} END {print n}')
</input/file tail -c +$(</input/file LC_CTYPE=C awk '
    {pos+=length($0 RS)}        # pos contains the current byte offset in the file
    /^---$/ {last=pos}          # last contains the byte offset after the last separator
    END {print last+1}          # print characters from last (+1 because tail counts from 1)
')

補遺:POSIX以上のバージョンがある場合、これはawkへの一般的な拡張に依存する単純な1パスバージョンで、レコードセパレーターRSを正規表現にすることができます(POSIXは単一文字のみを許可します)。完全に正しくはありません。ファイルがレコード区切り文字で終わっている場合、空のレコードではなく、最後のレコード区切り文字の前にチャンクが出力されます。を使用した2番目のバージョンでRTは、この欠陥は回避されていますRTが、GNU awkに固有のものです。

awk -vRS='(^|\n)---+($|\n)' 'END{printf $0}'
gawk -vRS='(^|\n)---+($|\n)' 'END{if (RT == "") printf $0}'

@ギレス:正常sedに動作していますが、awkサンプルを実行できません。それはハングします...そして3番目の例でエラーが発生します: cut -f ':' -t 1 ...カット:無効なオプション
-'t

@ fred.bear:私はそれがどのように起こったのかわかりません—私はすべてのスニペットをテストしましたが、どういうわけか、cut例のコピー/貼り付け後の編集を台無しにしました。このawk例に問題はありません。使用しているawkのバージョン、およびテスト入力は何ですか。
Gilles「SO-邪悪なことをやめよう」

...実際にはawkバージョンは機能しています.. 大きなファイルで非常に長い時間がかかります.. sedバージョンは0.470秒で同じファイルを処理しました..テストデータは非常に重み付けされています...単一の2つのチャンクのみ'---' 100万行の終わりから3行...
Peter.O

@Gilles ..(午前3時にテストを停止する必要があると思います。「2パス」awkの3つすべてを単一のユニットとして何とかテストしました:( ...それぞれを個別にテストしました。2番目は非常に高速です0.204秒 ...ハワーバー、最初の「2パス」awk出力のみ:「(標準入力)」(-lが原因のようです)... 3番目の「2パス」awkについては、私はしません何も出力しません...しかし、2番目の「2パス」は、提示されたすべてのメソッド(POSIXまたはその他
:)の中で最速

@ fred.bear:修正済み、修正済み。私のQAはこれらの短いスニペットにはあまり適していません。通常、コマンドラインからコピーして貼り付け、フォーマットしてからバグに気付き、再フォーマットするのではなくインラインで修正しようとします。文字のカウントが行のカウントよりも効率的かどうかを知りたいと思います(2番目と3番目の2パスメソッド)。
Gilles「SO-悪をやめなさい」

3

2パス戦略は正しいようです。sedの代わりに使用しますawk(1)。2つのパスは次のようになります。

$ LINE=`awk '/^---$/{n=NR}END{print n}' file`

行番号を取得します。そして、その行番号から始まるすべてのテキストをエコーし​​ます:

$ awk "NR>$LINE" file

これは過度のバッファリングを必要としないはずです。


そして、それらを組み合わせることができます:awk -v line=$(awk '/^---$/{n=NR}END{print n}' file) 'NR>line' file
グレン・ジャックマン2011年

私は他の提出物を時間をかけてテストしてきたので、上記の「glen jackman's」もスニペットでテストしました。0.352秒かかります(同じデータファイルが私の回答に記載されています)... awkは最初に可能だと思ったよりも速いというメッセージが表示され始めています(sedはそれと同じくらい良いと思いましたが、それは「コース用の馬」の場合のようです)...
Peter.O

これらすべてのスクリプトのベンチマークを確認することは非常に興味深いです。いい仕事フレッド。
Mackie Messer

最速のソリューションは、実際に入力ファイルを逆に読み取るtactailを使用します。さて、もしawkだけが入力ファイルを逆に読むことができたら…
Mackie Messer

3
lnum=$(($(sed -n '/^---$/=' file | sed '$!d') +1)); sed -n "${lnum},$ p" file 

最初のsed行は "---"行の行番号を出力します...
2番目の行sedは最初のsedの出力から最後の番号を抽出します...
1をその番号に追加して "ccc"ブロックの開始を取得します...
3番目「ccc」ブロックの先頭からEOFへの「sed」出力

更新 (Gillesメソッドに関する修正情報を含む)

さて、私は方法についてwondereingたグレン・ジャックマンさんは、 tac私の(執筆時点で)3つの答えを、時間がテストされたので...テストファイル(複数可)はそれぞれ、(自分の行番号の)100万行が含まれ、実行します。
すべての答えは期待通りでした...

ここが時代です


Gilles sed(シングルパス)

# real    0m0.470s
# user    0m0.448s
# sys     0m0.020s

Gilles awk(シングルパス)

# very slow, but my data had a very large data block which awk needed to cache.

ジル ' ツーパス '(最初の方法)

# real    0m0.048s
# user    0m0.052s
# sys     0m0.008s

Gillesの「2パス」(2番目の方法)... 非常に高速

# real    0m0.204s
# user    0m0.196s
# sys     0m0.008s

ジル ' ツーパス '(3番目の方法)

# real    0m0.774s
# user    0m0.688s
# sys     0m0.012s

Gilles 'gawk'(RTメソッド)... 非常に高速ですが、POSIXではありません。

# real    0m0.221s
# user    0m0.200s
# sys     0m0.020s

グレン・ジャックマン ... 非常に高速ですが、POSIXではありません。

# real    0m0.022s
# user    0m0.000s
# sys     0m0.036s

fred.bear

# real    0m0.464s
# user    0m0.432s
# sys     0m0.052s

マッキーメッサー

# real    0m0.856s
# user    0m0.832s
# sys     0m0.028s

好奇心から、私の2パスバージョンのどれをテストし、どのバージョンのawkを使用しましたか?
Gilles「SO-邪悪なことをやめよう」

@ギレス:私はGNU Awk 3.1.6を使用しました(Ubuntu 10.04では4 GB RAM)。すべてのテストでは、最初の「チャンク」に100万行、次に「マーカー」に続いて2つの「データ」行があります... 100,000行の小さいファイルを処理するのに15.540秒かかりましたが、1,000,000行の場合、現在実行しており、これまで25分以上経過しています。1つのコアを100%まで使用しています...今すぐ強制終了します...さらにインクリメンタルテストがあります:lines = 100000(0m16.026s)-lines = 200000(2m29.990s)-lines = 300000(5m23。 393秒)-lines = 400000(11分9.938秒)
Peter.O

エラー:上記のコメントで、「2パス」のawk参照がありませんでした。上記の詳細は「シングルパス」のawkに関するものです... awkバージョンは正しいです...私はあなたの答えの下で異なる「2パス」バージョンについてさらにコメントしました(上記の時間結果が変更されました)
Peter.O


0

あなたはただ使うことができます ed

ed -s infile <<\IN
.t.
1,?===?d
$d
,p
q
IN

動作方法:t現在の(.)行を複製します- ed開始時に常に最後の行です(区切り文字が最後の行にある場合に備えて)、1,?===?d前の一致までのすべての行を削除します(edまだ最後の行にあります) )次に$d、(重複した)最後の行を削除し,p、テキストバッファーを出力(置き換えてwファイルを編集して)し、最後にをq終了しedます。


入力に少なくとも1つの区切り文字があることがわかっている場合(および、それが出力されているかどうかは問題ではありません)

sed 'H;/===/h;$!d;x' infile

短くなります。
仕組み:H古いバッファーにすべての行を追加hし、一致した場合に古いバッファーを上書きします。バッファー(およびd自動印刷)を変更$すると、最初の行を除くすべての行が削除されますx

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