如何列出gz文件的内容而不在python中提取它?

Posted

技术标签:

【中文标题】如何列出gz文件的内容而不在python中提取它?【英文标题】:How do I list contents of a gz file without extracting it in python? 【发布时间】:2015-11-08 08:33:04 【问题描述】:

我有一个.gz 文件,我需要使用 python 获取其中的文件名。

这个问题和this one一样

唯一的区别是我的文件是 .gz 而不是 .tar.gz 所以 tarfile 库在这里没有帮助我

我正在使用 requests 库来请求 URL。响应是一个压缩文件。

这是我用来下载文件的代码

response = requests.get(line.rstrip(), stream=True)
        if response.status_code == 200:
            with open(str(base_output_dir)+"/"+str(current_dir)+"/"+str(count)+".gz", 'wb') as out_file:
                shutil.copyfileobj(response.raw, out_file)
            del response

此代码例如下载名称为1.gz 的文件。现在,如果我使用存档管理器打开文件,该文件将包含类似 my_latest_data.json 的内容

我需要提取文件,输出为my_latest_data.json

这是我用来提取文件的代码

inF = gzip.open(f, 'rb')
outfilename = f.split(".")[0]
outF = open(outfilename, 'wb')
outF.write(inF.read())
inF.close()
outF.close()

outputfilename 变量是我在脚本中提供的字符串,但我需要真实的文件名 (my_latest_data.json)

【问题讨论】:

问题是 gzip 只是压缩,不一定是存档。里面可能连一个manifest都看不到。 错误是什么?你试过的代码在哪里?您的问题不清楚。 补充@zxq9所说的,gzip与Zip文件(存档)的不同之处在于它只能“包含”一个文件。它可能只有原始文件名。 @I'L'L 请检查最后的编辑 @JonathonReinhart 我决定在答案中更好地充实这一点——我想 OP 并不是唯一一个想知道为什么会这样的人。也就是说,OP 可能想让这个问题在本质上更笼统一些,以便其他人可以找到它。 【参考方案1】:

你不能,因为 Gzip 不是存档格式。

这本身就是一个废话解释,所以让我比我在评论中所做的更详细地分解一下......

只是压缩

“只是一个压缩系统”意味着 Gzip 对输入字节(通常来自文件)进行操作并输出压缩字节。你无法知道里面的字节是代表多个文件还是只是一个文件——它是只是一个被压缩的字节流。例如,这就是您可以通过网络接受 gzip 压缩数据的原因。它的 bytes_in -> bytes_out。

什么是清单?

清单是存档中的标头,用作存档的目录。请注意,现在我使用术语“存档”而不是“压缩字节流”。存档意味着它是清单引用的文件或段的集合——压缩的字节流只是字节流。

Gzip 里面到底有什么?

.gz 文件内容的简化描述如下:

    带有特殊数字的标头表示其 gzip、版本和时间戳(10 字节) 可选标题;通常包括原始文件名(如果压缩目标是文件) 主体 -- 一些压缩的有效载荷 末尾的 CRC-32 校验和(8 个字节)

就是这样。没有清单。

另一方面,存档格式内部会有一个清单。这就是 tar 库的用武之地。Tar 只是一种将一堆位放在一个文件中的方法,并在前面放置一个清单,让您知道原始文件的名称以及它们之前的大小连接到存档中。因此,.tar.gz 如此普遍。

有一些实用程序允许您一次解压缩 gzip 文件的一部分,或者只在内存中解压缩它,然后让您检查清单或其中可能存在的任何内容。但是任何清单的详细信息都特定于其中包含的存档格式。

请注意,这与 zip 存档不同。 Zip 一种存档格式,因此包含清单。 Gzip 是一个压缩库,和 bzip2 和朋友一样。

【讨论】:

您可能想解释一下什么是“清单”。不清楚它是否描述了存档中包含的文件列表。 普通的存档管理器如何能够向我显示 .gz 中的文件? @Fanooos 他们将在 gzip 内部窥视以查看它是否包含存档。如上所述,有一些实用程序可以让您即时操作 gzip 压缩的数据(例如 zcat -- 使用 man zcat 来阅读它 -- 非常巧妙),因此它可以通过这种方式检查内部文件头而无需太多开销。这也是为什么现在有这么多 .ps 文件是“.ps.gz”的原因——时间/空间的权衡在今天普遍是有利的。 @JonathonReinhart 完成。谢谢。【参考方案2】:

正如另一个答案中所述,只有当我取出复数形式时,您的问题才有意义:“我有一个 .gz 文件,我需要在其中获取 file 的名称蟒蛇。”

gzip 标头中可能有也可能没有文件名。 gzip 实用程序通常会忽略标头中的名称,并解压缩为与.gz 文件同名的文件,但去掉了.gz。例如。你的1.gz 会解压成一个名为1 的文件,即使标题中包含文件名my_latest_data.json。 gzip 的 -N 选项将使用标头中的文件名(以及标头中的时间戳),如果有的话。所以gzip -dN 1.gz 将创建文件my_latest_data.json,而不是1

您可以通过手动处理标头在 Python 中找到标头中的文件名。您可以在gzip specification中找到详细信息。

    验证前三个字节是1f 8b 08。 保存第四个字节。叫它flags。如果flags & 8 为零,则放弃——标题中没有文件名。 跳过接下来的六个字节。 如果flags & 2 不为零,则跳过两个字节。 如果flags & 4 不为零,则读取接下来的两个字节。考虑到它们是小端顺序,从这两个字节中制作一个整数,称之为xlen。然后跳过xlen 字节。 我们已经知道flags & 8 不为零,因此您现在位于文件名处。读取字节,直到达到零字节。不包括零字节的那些字节是文件名。

【讨论】:

【参考方案3】:

注意:从 Python 3 开始,此答案已过时。


使用 Mark Adler 回复中的提示和对 gzip 模块的一些检查,我设置了从 gzip 文件中提取内部文件名的函数。我注意到 GzipFile 对象有一个名为 _read_gzip_header() 的私有方法,它几乎可以获取文件名,所以我基于它做了

import gzip

def get_gzip_filename(filepath):
    f = gzip.open(filepath)
    f._read_gzip_header()
    f.fileobj.seek(0)
    f.fileobj.read(3)
    flag = ord(f.fileobj.read(1))
    mtime = gzip.read32(f.fileobj)
    f.fileobj.read(2)
    if flag & gzip.FEXTRA:
        # Read & discard the extra field, if present
        xlen = ord(f.fileobj.read(1))
        xlen = xlen + 256*ord(f.fileobj.read(1))
        f.fileobj.read(xlen)
    filename = ''
    if flag & gzip.FNAME:
        while True:
            s = f.fileobj.read(1)
            if not s or s=='\000':
                break
            else:
                filename += s
    return filename or None

【讨论】:

好的,谢谢,我只在 python 2.7 上测试过 我注意到在 Python 3 中没有gzip.read32(f.fileobj)。难道连替代方法都没有吗? @Memmo 现在看看我的回答;它确实被删除了。【参考方案4】:

The Python 3 gzip library discards this information 但您可以采用链接周围的代码来做其他事情。

正如本页其他答案中所述,此信息无论如何都是可选的。但是如果你需要查看它是否在那里,它也不是不可能的。

import struct


def gzinfo(filename):
    # Copy+paste from gzip.py line 16
    FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16
    
    with open(filename, 'rb') as fp:
        # Basically copy+paste from GzipFile module line 429f
        magic = fp.read(2)
        if magic == b'':
            return False

        if magic != b'\037\213':
            raise ValueError('Not a gzipped file (%r)' % magic)

        method, flag, _last_mtime = struct.unpack("<BBIxx", fp.read(8))

        if method != 8:
            raise ValueError('Unknown compression method')

        if flag & FEXTRA:
            # Read & discard the extra field, if present
            extra_len, = struct.unpack("<H", fp.read(2))
            fp.read(extra_len)
        if flag & FNAME:
            fname = []
            while True:
                s = fp.read(1)
                if not s or s==b'\000':
                    break
                fname.append(s.decode('latin-1'))
            return ''.join(fname)
        
def main():
    from sys import argv
    for filename in argv[1:]:
        print(filename, gzinfo(filename))

if __name__ == '__main__':
    main()

这会将原始代码中的异常替换为模糊的 ValueError 异常(如果您打算更广泛地使用它,并将其转换为适当的模块,您可以 import)并使用通用的 read() 函数,而不是特定的 _read_exact() 方法,该方法会遇到一些麻烦以确保它获得所需的字节数(如果您愿意,也可以取消它)。

【讨论】:

关于一个相关问题,@RandomDavis points out this bugs.python.org/issue1159051 干得好@triplee!我当时炒作是对的! ;) 今天下午(意大利时区)我试试看! 这最终与 Python 2 的答案非常相似,我注意到,尽管使用 struct 可能是一种改进。此代码似乎也适用于 Python 2。

以上是关于如何列出gz文件的内容而不在python中提取它?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中列出压缩的 tar 文件的内容

如何在python中读取pdf文件而不在unix中转换它? [复制]

从 ls 中提取子字符串

提取并删除目录中的所有.gz - Linux

使用python ZIPFile提取gz文件[重复]

如何在 python 中提取之前重命名压缩文件的内容?