前几天,我从远程服务器收集了一些日志,然后不假思索地将文件压缩到一个文件中,而不是将目录添加到压缩包中。我可以手动分离出一些日志文件,但其中一些已经被 gzip 压缩了。所以原始文件看起来像:
ex_access.log
ex_access.log.1.gz
ex_access.log.2.gz
ex_debug.log
ex_debug.log.1.gz
ex_debug.log.2.gz
ex_update.log
ex_update.log.1.gz
ex_update.log.2.gz
并被压缩到 exlogs.gz 中,如您所料,解压后它是一个包含所有原始文件的文件。有没有办法分离出原始的gz文件,以便它们可以正常解压缩而不是打印出二进制文件:
^_<8B>^H^H<9B>C<E8>a^@
^Cex_access.log.1^@<C4><FD><U+076E>-Kr<9D> <DE><F7>S<9C>^W<E8><CE><F0><FF><88>y[<D5><EA>+<A1>^EHuU<A8>^K<B6><94><AA>L4E^R̤^Z^B<EA><E1><DB>}<AE>̳<B6><D6>I<C6><F8><9C><DB><C6>
<F1>@G`<E6><D6><FE><E0>3<C2><C3>ٰ̆|<E4><FC><BB>#<FD><EE><B8>~9<EA>+<A7>W+<FF><FB><FF><F6><9F><FE><97><FF><E3><97><FF><FD>^Z<E3><FF><F8><E5><FF><FE><CB><C7><FF>Iy<FC>?<8E><F9>?<F3>?<EF><B5><F7><F9><BF><FF>ß<FF>
[etc]
是的,我可以再次收集日志(因为我确实有意识保持原件完好无损),但是获得访问服务器的批准是一件痛苦的事情,如果可能的话,我想避免它。
编辑:我使用的命令是
gzip -c ex_* > exlogs.gz
碰巧的是,in
gzip -c file1 file2 > result
确实gzip
为每个文件创建了两个单独的压缩流,甚至存储了文件名和文件的修改时间。它不允许您在解压缩时使用该信息,但您可以使用
perl
'IO::Uncompress::Gunzip
模块来代替。例如:并将该脚本称为
that-script < exlogs.gz
,它将使用其原始名称和修改时间(不包括未存储的亚秒部分gzip
)在当前工作目录中恢复文件。将文件 gzip 压缩到单个文件时,
gzip
会创建一个包含多个 gzip 流的文件,就像您首先单独压缩文件然后将它们连接起来一样。手册页中简要提到了此行为。
这意味着每个源文件都有一个单独的 gzip 标头(其中包含原始文件名)。所以原则上它们可以在解压时分开。
不幸的是,
gzip
开发人员选择不支持这一点gunzip
:取消连接文件并非易事,因为 gzip 标头和页脚都不包含压缩数据流的长度。这意味着,为了可靠地找到第二个流的开始,您需要对整个 deflate 数据流进行解码,这是解压缩整个数据流的一半。
据我所知,目前还没有工具可以只浏览数据流以找出它的结束位置,即使该领域有一些研究支持对 gzip 压缩文件内容的准随机访问。
幸运的是,一些编程库可用于单独解压缩 gzip 流,例如 Perl 的
IO::Uncompress::Gunzip
,如 Stéphane Chazelas 在他的回答中提到的,或者 Rust 的flate2
。最后,作为解决方案,我编写了工具gunzip-split。它单独解压缩每个文件,也可以解连接文件。对于后者,它解压缩每个文件,记录 gzip 流开始的偏移量,同时丢弃结果。这可以进一步优化,但即使对于千兆字节大小的文件也可以相当快地工作。
这有点复杂,但在使用以下要求时有效:
merged.gz
是清晰的 ASCII 数据和 gzip 文件的混合cat log0 log1.gz log2.gz log3 log4.gz > merged.gz
1F 8B
)大多数程序应该可用,
sponge
可以moreutils
通过手动写入临时文件来避免。做了什么:
gz_only.gz
文件最后一点使用
csplit
,只有在还有换行符的情况下才能拆分 - 所以这是在拆分之前引入并在之后删除。目前假设合并系统中的 gzip 压缩文件不超过 1000 个。我有点觉得 ASCII 和非 ASCII 的分离以及拆分可能会更优雅地完成
perl
,但我不熟悉。