如何列出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中提取它?的主要内容,如果未能解决你的问题,请参考以下文章