テールなしでgzファイルの最後の行をzgrepする方法



How Zgrep Last Line Gz File Without Tail



解決:

最も簡単な解決策は、ログローテーションを変更して小さなファイルを作成することです。

2番目に簡単な解決策は、ランダムアクセスをサポートする圧縮ツールを使用することです。



dictzip、BGZF、csioなどのプロジェクトはそれぞれ、gzip圧縮されたデータ内にさまざまな間隔で同期フラッシュポイントを追加します。これにより、プログラムでその追加情報を認識できるようになります。それは標準に存在しますが、バニラgzipは、デフォルトでもオプションでも、そのようなマーカーを追加しません。

これらのランダムアクセス対応ユーティリティによって圧縮されたファイルは、マーカー自体のためにわずかに大きくなります(おそらく2〜20%)が、gzipまたはこれらのマーカーを認識しない別のユーティリティ。



さまざまな圧縮形式でのランダムアクセスについては、この質問で詳細を学ぶことができます。

PeterCockによる「BlastedBioinformatics」ブログもあり、このトピックに関するいくつかの投稿があります。

  • BGZF-ブロックされ、より大きく、より良いGZIP! –ランダムアクセス付きのgzip(dictzipなど)
  • BZIP2へのランダムアクセス? –調査(結果:以下で行いますが、実行できません)
  • ブロックされたXZ形式(BXZF)へのランダムアクセス–ランダムアクセスサポートが改善されたxz

実験xz

xz(LZMA圧縮形式)は、実際にはブロックごとのレベルでランダムアクセスをサポートしていますが、デフォルトでは1つのブロックしか取得できません。



ファイルの作成

xzは複数のアーカイブを連結できます。その場合、各アーカイブには独自のブロックがあります。 GNUsplitはこれを簡単に行うことができます:

split -b 50M --filter 'xz -c' big.log > big.log.sp.xz  

This tells split to break big.log into 50MB chunks ( before compression) and run each one through xz -c, which outputs the compressed chunk to standard output. We then collect that standard output into a single file named big.log.sp.xz.

To do this without GNU, you'd need a loop:

split -b 50M big.log big.log-part for p in big.log-part*; do xz -c $p; done > big.log.sp.xz rm big.log-part*  

Parsing

You can get the list of block offsets with xz --verbose --list FILE.xz. If you want the last block, you need its compressed size (column 5) plus 36 bytes for overhead (found by comparing the size to hd big.log.sp0.xz |grep 7zXZ). Fetch that block using tail -c and pipe that through xz. Since the above question wants the last line of the file, I then pipe that through tail -n1:

SIZE=$(xz --verbose --list big.log.sp.xz |awk 'END { print $5 + 36 }') tail -c $SIZE big.log.sp.xz |unxz -c |tail -n1  

Side note

Version 5.1.1 introduced support for the --block-size flag:

xz --block-size=50M big.log  

However, I have not been able to extract a specific block since it doesn't include full headers between blocks. I suspect this is nontrivial to do from the command line .

Experiments with gzip

gzip also supports concatenation. I (briefly) tried mimicking this process for gzip without any luck. gzip --verbose --list doesn't give enough information and it appears the headers are too variable to find.

This would require adding sync flush points, and since their size varies on the size of the last buffer in the previous compression, that's too hard to do on the command line (use dictzip or another of the previously discussed tools).

I did apt-get install dictzip and played with dictzip, but just a little. It doesn't work without arguments, creating a (massive!) .dz archive that neither dictunzip nor gunzip could understand.

Experiments with bzip2

bzip2 has headers we can find. This is still a bit messy, but it works.

Creation

This is just like the xz procedure above:

split -b 50M --filter 'bzip2 -c' big.log > big.log.sp.bz2  

I should note that this is considerably slower than xz (48 min for bzip2 vs 17 min for xz vs 1 min for xz -0) as well as considerably larger (97M for bzip2 vs 25M for xz -0 vs 15M for xz), at least for my test log file.

Parsing

This is a little harder because we don't have the nice index. We have to guess at where to go, and we have to err on the side of scanning too much, but with a massive file, we'd still save I/O.

My guess for this test was 50000000 (out of the original 52428800, a pessimistic guess that isn't pessimistic enough for e.g. an H.264 movie.)

GUESS=50000000 LAST=$(tail -c$GUESS big.log.sp.bz2  |grep -abo 'BZh91AY&SY' |awk -F: 'END { print '$GUESS'-$1 }') tail -c $LAST big.log.sp.bz2 |bunzip2 -c |tail -n1  

This takes just the last 50 million bytes, finds the binary offset of the last BZIP2 header, subtracts that from the guess size, and pulls that many bytes off of the end of the file. Just that part is decompressed and thrown into tail.

Because this has to query the compressed file twice and has an extra scan (the grep call seeking the header, which examines the whole guessed space), this is a suboptimal solution. See also the below section on how slow bzip2 really is.

Perspective

Given how fast xz is, it's easily the best bet; using its fastest option (xz -0) is quite fast to compress or decompress and creates a smaller file than gzip or bzip2 on the log file I was testing with. Other tests (as well as various sources online) suggest that xz -0 is preferable to bzip2 in all scenarios.

 ————— No Random Access —————— ——————— Random Access ——————— FORMAT SIZE RATIO WRITE READ SIZE RATIO WRITE SEEK ————————— ————————————————————————————— ————————————————————————————— (original) 7211M 1.0000 - 0:06 7211M 1.0000 - 0:00 bzip2 96M 0.0133 48:31 3:15 97M 0.0134 47:39 0:00 gzip 79M 0.0109 0:59 0:22 dictzip 605M 0.0839 1:36 (fail) xz -0 25M 0.0034 1:14 0:12 25M 0.0035 1:08 0:00 xz 14M 0.0019 16:32 0:11 14M 0.0020 16:44 0:00 

タイミングテストは包括的ではなく、平均化もせず、ディスクキャッシュが使用されていました。それでも、それらは正しく見えます。からのオーバーヘッドはごくわずかです分割して、1つではなく145の圧縮インスタンスを起動します(これはネットの場合もあります) 利得 マルチスレッドではないユーティリティが複数のスレッドを消費できる場合)。