次のように表示された行があるテキストがあります。
aaa
---
bbb
---
ccc
最後のトークン(排他)のテキストをEOFにインポートする必要があります。この場合、
ccc
POSIX.2にエレガントな方法はありますか?今度は2回の実行を使用します。最初の実行nl
とgrep
その行番号を持つ最後の実行。その後、行番号を抽出して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
欠陥を防ぎますが、RT
GNU 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以外のすべての行を削除します。h
d
$
x