POSIX.2の最後のトークンからEOFにテキストをインポートする

POSIX.2の最後のトークンからEOFにテキストをインポートする

次のように表示された行があるテキストがあります。

aaa
---
bbb
---
ccc

最後のトークン(排他)のテキストをEOFにインポートする必要があります。この場合、

ccc

POSIX.2にエレガントな方法はありますか?今度は2回の実行を使用します。最初の実行nlgrepその行番号を持つ最後の実行。その後、行番号を抽出してsed問題ブロックを抽出するために使用しました。

テキストセグメントは非常に大きくなる可能性があるため、テキストをバッファに追加するなど、いくつかのテキストを追加する方法を使用することが心配です。マーカーが見つかると、バッファを空にしてEOFの最後のチャンクバッファを取得します。

答え1

セグメントが非常に大きくない限り(たとえば、大容量ファイルシステムを制御する小さな組み込みシステムであるため、実際にそのような多くのRAMを節約できない場合)、単一パスは実際にはより良いアプローチです。速度が速くなるだけでなく、最も重要なのは、ソースをストリームにすることができ、読み取り後に保存していないすべてのデータが失われることです。 sedもできますが、これは実際にawkの仕事です。

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以上のものがある場合、レコード区切り文字がRS正規表現になることを可能にするawkの一般的な拡張に依存する単純な使い捨てバージョンがあります(POSIXは単一文字のみを許可します)。完全に正確ではありません。ファイルがレコード区切り文字で終わる場合は、空のレコードの代わりに最後のレコード区切り文字の前のブロックを印刷します。使用される2番目のバージョンはRT欠陥を防ぎますが、RTGNU awkに固有のものです。

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

答え2

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

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

修正する (Gilles メソッドに関する修正情報)

わかりました 私は方法を知りたいグレンジャックマン tacだから私は3つの答え(作成時)を時間テストしました...各テストファイルには100万行(各行番号)が含まれていました。
すべての答えは予想通りです...

ここに時間があります...


ザイルズ sed(一方通行)

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

ザイルズ awk(一方通行)

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

ザイルズ「ステップ2」(最初の方法)

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

ザイルズ「ダブルパス」(2番目の方法)...とても速い

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

ザイルズ「2回パス」(3回目の方法)

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

ザイルズ「gawk」(RT方式)...とても速い、POSIXではありません。

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

グレンジャックマン...とても速い、POSIXではありません。

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

フレッドベア

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

マイキメッサー

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

答え3

2段階の戦略が正しいように見えました。代わりにsedを使用しますawk(1)。 2つのチャンネルは次のとおりです。

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

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

$ awk "NR>$LINE" file

あまりにも多くのバッファリングは必要ありません。

答え4

ただ使えます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一致するものが見つかった場合は前のバッファを上書きし、バッファが変更されたら(そして自動的に印刷される)、lat以外のすべての行を削除します。hd$x

関連情報