跳到主要内容
AZ Tools

gzip(.gz)结构检查器

.gz 不是归档文件,而是一条流:一个或多个成员首尾相接,每个成员都有自己的 10 字节头部、自己的 deflate 流,以及自己的 8 字节尾部记录,里面放着 CRC32 和 ISIZE(对 2^32 取模的解压后长度)。本页会走遍所有成员,并显示每个字段真正说了什么:1f 8b 魔数与压缩方法,FLG 的五个标志位(FTEXT、FHCRC、FEXTRA、FNAME、FCOMMENT)以及它们承诺的可选字段,换算成日期的 MTIME——0 表示从未设置,可复现构建写的正是这个值——XFL(2 为最高压缩,4 为最快算法)和按名称解释的操作系统字节,还有拆分成两字节 ID 与负载的 FEXTRA 子字段、原始文件名、注释和头部 CRC16。然后它会校验。每个成员都由浏览器自己解压,并在结果上重新计算 CRC32,因此尾部记录是被核对的,而不是被照抄的:CRC 对不上的文件确实损坏了,而这种损坏平时往往要等到恢复进行到一半才暴露出来,那时原件通常已经没有了。同一遍处理也给出真实的解压后大小,这就是这里的合计可能与 gzip -l 不同的原因。gzip -l 读取最后一个成员的 ISIZE,并把它当作整个文件的大小,所以对于 cat a.gz b.gz 拼出来的文件,或 pigz 之类并行压缩器按独立块写出的文件,它的答案正好少了前面所有成员的内容。ISIZE 只有 32 位,超过 4 GB 的成员只能保存取模后的长度,仅凭这个字段无法还原真实大小。最后一段尾部记录之后的垃圾字节、在流中途断掉的成员,以及根本不是 gzip 的文件,都会被明确命名,而不是含糊地解析一半。成员内部保存的文件名——gunzip -N 会还原成的那个名字,可能是一条路径,也可能与你手上的文件完全不同——按存储原样显示;该字段按规范是 ISO-8859-1,实际却装着本地编码的字节,所以两种读法不一致时会同时列出。

使用方法

  1. 把 .gz、.tgz 或任意 gzip 流拖到方框里。解析和解压都在页面内完成,不会上传。
  2. 先看概览:文件里究竟有几个成员、真实的解压后总量,以及所有 CRC32 是否都对得上。
  3. 展开某个成员查看头部:五个标志位、MTIME、XFL、操作系统字节、保存的文件名、注释和 FEXTRA 子字段。
  4. 对比尾部记录的两组数值:尾部的 CRC32 与数据的实际 CRC32,ISIZE 与实际解压大小。两者都一致,正是 gzip -t 所检查的内容。
  5. 如果是多成员文件,在引用来自 gzip -l 的大小之前,先留意它与真实合计之间的差距。

常见问题

为什么 gzip -l 报告的大小和本页不同?
因为 gzip -l 根本不解压。它直接跳到文件末尾的四个字节,读出那个 ISIZE 并当作解压后大小打印出来。对单成员文件这是对的。但对由多个成员拼接而成的文件——cat a.gz b.gz、追加到归档后面的轮转日志、按独立块写出的 pigz 或 bgzip——这四个字节只描述最后一个成员,于是 gzip -l 把前面所有成员的量都漏掉了。本页则解压每个成员,把实际吐出的字节加起来,所以合计可能大得多。这也意味着 gzip -l 瞬间返回而本页不会:诚实答案的代价就是把文件解压一遍。
MTIME 为 0 是什么意思?这个字段记录的是哪个时间?
MTIME 是原始文件的修改时间,以 UTC 的 Unix 纪元秒表示,既不是执行压缩的时刻,也不是磁盘上 .gz 文件的时间戳。0 表示当时没有可用时间,或者有意不保存:gzip -n 会特意写 0,可复现构建也一样,因为时间戳正是同样内容两次构建却字节不同的经典原因。从管道压缩同样得到 0,因为没有可取时间的文件。该字段只有四个字节,所以 2106 年之后的日期根本无法表示,而超过 2^31 的值究竟算遥远的未来还是负数,各实现的处理并不一致。
原始文件名显示成乱码,是文件坏了吗?
不是,坏的是格式规定。RFC 1952 说 FNAME 是 ISO-8859-1(Latin-1)字符串,但 Linux 和 macOS 上的 gzip 直接写入本地编码生成的名字字节,如今几乎都是 UTF-8。于是超出 ASCII 的名字只能在使用相同编码的系统之间完整往返:在 UTF-8 系统上写下的名字若按 Latin-1 读出,就会变成常见的重音乱码。本页会按规范解码显示该字段,并在同样的字节也是合法 UTF-8 且读法不同时,把 UTF-8 的读法并列显示,让你同时看到两个候选名字,自行判断写入者本来想表达哪一个。
CRC32 不匹配到底能证明什么?匹配又不能证明什么?
不匹配意味着从 deflate 流里出来的字节,与写入时进去的字节不同:存储介质上翻转的比特、被截断后又拼接的传输、对压缩数据的改动。这与 gunzip 报出的 “invalid compressed data — crc error” 是同一种失败,只不过这里不需要把输出写到任何地方就能看到。反过来,匹配只能证明数据和压缩器当时看到的一致。CRC32 是 32 位的错误检测码,不是签名:它以极高的概率发现意外损坏,但能改动数据的人同样能重新算出 CRC。要验证真实性,需要对整个文件做哈希或签名,而不是看它的尾部记录。
能看到 .tar.gz 里面有哪些文件吗?
不能,任何只读 gzip 的工具都做不到。gzip 只压缩一条字节流,并不知道里面装的是什么:没有目录表,没有逐文件的条目,也没有办法在不解压前面内容的情况下单独取出一个文件。.tar.gz 是把具备这种结构的 tar 归档整体交给 gzip 压缩的结果。本页能告诉你的只是 gzip 这一层记录的信息:有几个成员、每个头部里的原始文件名(常常是 archive.tar)、时间戳,以及解压出来的数据量。要列出条目,需要用 tar 读取器去读已经解压的那条流。
FEXTRA 子字段有什么用?
FEXTRA 是一个扩展点:一串带长度前缀的子字段,每个子字段由两字节 ID、自身长度和负载组成,读取方遇到不认识的 ID 必须跳过。它的实际用途基本都是让 gzip 流可以随机定位。BGZF 是 bgzip 以及基因组学里所有 BAM 文件的基础,它用 “BC” 子字段写下每个块的大小,读取方就能直接跳到块边界;dictzip 出于同样的理由把分块长度表放在 “RA” 下面。两者仍然是普通的 gzip 文件,gunzip 照常能读,这正是该机制的精妙之处。看到不常见的子字段,通常就是文件出自这类工具的线索。

相关工具