如何在 python 中列出 gz 文件的内容而不解压缩它?
How do I list contents of a gz file without extracting it in python?
我有一个 .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 不是一种存档格式。
这本身就是一个废话解释,所以让我比我在评论中做的更详细一点...
只是压缩
"just a compression system" 意味着 Gzip 对输入字节(通常来自文件)进行操作并输出压缩字节。您无法知道里面的字节是代表多个文件还是只代表一个文件——它只是一个已被压缩的字节流。例如,这就是您可以通过网络接受 gzip 压缩数据的原因。它的 bytes_in -> bytes_out.
什么是清单?
清单是存档中的 header,充当存档内容的 table。请注意,现在我使用的是 "archive" 而不是 "compressed stream of bytes"。存档意味着它是清单所引用的 collection 文件或段 -- 压缩的字节流 只是 字节流。
Gzip 里面到底有什么?
对 .gz 文件内容的稍微简化的描述是:
- 一个 header 带有一个特殊数字来表示它的 gzip、版本和时间戳(10 字节)
- 可选headers;通常包括原始文件名(如果压缩 target 是一个文件)
- body -- 一些压缩的负载
- 最后一个 CRC-32 校验和(8 字节)
就是这样。没有清单。
另一方面,存档格式内部会有一个清单。这就是 tar 库的用武之地。 Tar 只是一种将一堆位推到一个文件中的方法,并在前面放置一个清单,让您知道原始文件的名称文件以及它们在连接到存档中之前的大小。因此,.tar.gz
如此普遍。
有些实用程序允许您一次解压缩 gzip 文件的一部分,或者只在内存中解压缩它,然后让您检查清单或其中可能包含的任何内容。但是任何清单的详细信息都特定于其中包含的存档格式。
请注意,这与 zip 存档不同。 Zip 是 一种存档格式,因此包含清单。 Gzip 是一个压缩 库,就像 bzip2 和朋友一样。
如另一个答案所述,只有我去掉复数形式,你的问题才有意义:“我有一个 .gz
文件,我需要获取 文件的名称 在其中使用 python."
gzip header 中可能有也可能没有文件名。 gzip 实用程序通常会忽略 header 中的名称,并解压缩到与 .gz
文件同名的文件,但删除 .gz
。例如。您的 1.gz
将解压缩为名为 1
的文件,即使 header 中的文件名为 my_latest_data.json
。 gzip 的 -N 选项将使用 header 中的文件名(以及 header 中的时间戳),如果有的话。所以 gzip -dN 1.gz
会创建文件 my_latest_data.json
,而不是 1
.
您可以通过手动处理header在Python中找到header中的文件名。您可以在 gzip specification.
中找到详细信息
- 验证前三个字节是
1f 8b 08
。
- 保存第四个字节。称之为
flags
。如果 flags & 8
为零,则放弃 -- header. 中没有文件名
- 跳过接下来的六个字节。
- 如果
flags & 2
不为零,则跳过两个字节。
- 如果
flags & 4
不为零,则读取接下来的两个字节。考虑到它们是小端顺序,从这两个字节中生成一个整数,称之为 xlen
。然后跳过 xlen
字节。
- 我们已经知道
flags & 8
不为零,所以您现在位于文件名处。读取字节直到达到零字节。直到但不包括零字节的那些字节是文件名。
注意:此答案自 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=='[=10=]0':
break
else:
filename += s
return filename or None
The Python 3 gzip
library discards this information 但您可以采用 link 周围的代码来做其他事情。
如本页其他答案所述,此信息无论如何都是可选的。但如果你需要查看它是否存在,也不是不可能检索。
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'73':
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'[=10=]0':
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()
方法,该方法会遇到一些麻烦以确保它得到它请求的字节数(如果你想要的话,这也可以取消到).
我有一个 .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 不是一种存档格式。
这本身就是一个废话解释,所以让我比我在评论中做的更详细一点...
只是压缩
"just a compression system" 意味着 Gzip 对输入字节(通常来自文件)进行操作并输出压缩字节。您无法知道里面的字节是代表多个文件还是只代表一个文件——它只是一个已被压缩的字节流。例如,这就是您可以通过网络接受 gzip 压缩数据的原因。它的 bytes_in -> bytes_out.
什么是清单?
清单是存档中的 header,充当存档内容的 table。请注意,现在我使用的是 "archive" 而不是 "compressed stream of bytes"。存档意味着它是清单所引用的 collection 文件或段 -- 压缩的字节流 只是 字节流。
Gzip 里面到底有什么?
对 .gz 文件内容的稍微简化的描述是:
- 一个 header 带有一个特殊数字来表示它的 gzip、版本和时间戳(10 字节)
- 可选headers;通常包括原始文件名(如果压缩 target 是一个文件)
- body -- 一些压缩的负载
- 最后一个 CRC-32 校验和(8 字节)
就是这样。没有清单。
另一方面,存档格式内部会有一个清单。这就是 tar 库的用武之地。 Tar 只是一种将一堆位推到一个文件中的方法,并在前面放置一个清单,让您知道原始文件的名称文件以及它们在连接到存档中之前的大小。因此,.tar.gz
如此普遍。
有些实用程序允许您一次解压缩 gzip 文件的一部分,或者只在内存中解压缩它,然后让您检查清单或其中可能包含的任何内容。但是任何清单的详细信息都特定于其中包含的存档格式。
请注意,这与 zip 存档不同。 Zip 是 一种存档格式,因此包含清单。 Gzip 是一个压缩 库,就像 bzip2 和朋友一样。
如另一个答案所述,只有我去掉复数形式,你的问题才有意义:“我有一个 .gz
文件,我需要获取 文件的名称 在其中使用 python."
gzip header 中可能有也可能没有文件名。 gzip 实用程序通常会忽略 header 中的名称,并解压缩到与 .gz
文件同名的文件,但删除 .gz
。例如。您的 1.gz
将解压缩为名为 1
的文件,即使 header 中的文件名为 my_latest_data.json
。 gzip 的 -N 选项将使用 header 中的文件名(以及 header 中的时间戳),如果有的话。所以 gzip -dN 1.gz
会创建文件 my_latest_data.json
,而不是 1
.
您可以通过手动处理header在Python中找到header中的文件名。您可以在 gzip specification.
中找到详细信息- 验证前三个字节是
1f 8b 08
。 - 保存第四个字节。称之为
flags
。如果flags & 8
为零,则放弃 -- header. 中没有文件名
- 跳过接下来的六个字节。
- 如果
flags & 2
不为零,则跳过两个字节。 - 如果
flags & 4
不为零,则读取接下来的两个字节。考虑到它们是小端顺序,从这两个字节中生成一个整数,称之为xlen
。然后跳过xlen
字节。 - 我们已经知道
flags & 8
不为零,所以您现在位于文件名处。读取字节直到达到零字节。直到但不包括零字节的那些字节是文件名。
注意:此答案自 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=='[=10=]0':
break
else:
filename += s
return filename or None
The Python 3 gzip
library discards this information 但您可以采用 link 周围的代码来做其他事情。
如本页其他答案所述,此信息无论如何都是可选的。但如果你需要查看它是否存在,也不是不可能检索。
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'73':
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'[=10=]0':
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()
方法,该方法会遇到一些麻烦以确保它得到它请求的字节数(如果你想要的话,这也可以取消到).