跳转至

6.15.Compression_Archiving

import gzip
import os
import shutil
from toolkit.blog.util import get_work_dir

FileDir = os.path.join(get_work_dir(), "datasets/file")

gzip

gzip 模块中包含一些常量,这是 DEFLATE 算法生成的压缩文件的一些标记字段。

gzip.FCOMMENT
gzip.FEXTRA
gzip.FHCRC
gzip.FNAME
gzip.FTEXT
gzip.GzipFile
gzip.READ
gzip.WRITE

处理比特流

压缩内存中的比特流 (字节流):

gzip.compress(b"this is a test.")
b'\x1f\x8b\x08\x00\xcc\x88\xaa_\x02\xff+\xc9\xc8,V\x00\xa2D\x85\x92\xd4\xe2\x12=\x00\xc3\xf7\x06N\x0f\x00\x00\x00'

解压缩内存中的比特流:

该比特流必须是使用 gzip 对应的 DEFLATE 压缩算法得到的比特流,否则无法使用对应的算法解压缩。

gzip.decompress(gzip.compress(b"this is a test."))
b'this is a test.'

解压 .gz 文件

gzip-1.3.14.tar.gz 会被解压为 gzip-1.3.14.tar 文件。

gz_file_path = os.path.join(FileDir, "gzip-1.3.14.tar.gz")
ext_file_path = os.path.join(FileDir, "gzip-1.3.14.tar")
# 创建 gzip 对象
gz_file = gzip.GzipFile(gz_file_path)
with open(ext_file_path, "wb+") as f:
    f.write(gz_file.read())
gz_file.close()

也可以使用 gzip.open 函数打开 gz 文件,实际这也是调用 gzip.GzipFile 类。

# gzip.open 相当于 gzip.GzipFile
with gzip.open(gz_file_path, "rb") as output:
    with open(ext_file_path, "wb") as f:
        f.write(output.read())

tarfile

import tarfile
import time

判断文件是否为tar文档,如果文件不存在,会引发IO ERROR。

tar_file_path = os.path.join(FileDir, "gzip-1.3.14.tar")
tarfile.is_tarfile(tar_file_path)
True

获取元数据

使用 .getnames() 来读取压缩文件中所有文件的文件名。

with tarfile.open(tar_file_path, "r") as t:
    print(t.getnames()[0:5])
['gzip-1.3.14', 'gzip-1.3.14/zgrep.1', 'gzip-1.3.14/configure.ac',
'gzip-1.3.14/gzexe.1', 'gzip-1.3.14/gunzip.1']

通过 .getmembers().getmember() 函数来获取元数据。

with tarfile.open(tar_file_path, "r") as t:
    for member_info in t.getmembers():
        print(member_info.name)
        print("  Modified:", time.ctime(member_info.mtime))
        print("  Mode    :", oct(member_info.mode))
        print("  Type    :", member_info.type)
        print("  Size    :", member_info.size, "bytes")
        print()
        break
gzip-1.3.14
  Modified: Sat Oct 31 02:54:33 2009
  Mode    : 0o777
  Type    : b'5'
  Size    : 0 bytes

如果一个所含文件的文件名已知,可使用 getmember() 函数获取其所对应的 TarInfo 对象。

with tarfile.open(tar_file_path, "r") as t:
    for filename in ["gzip-1.3.14/doc/Makefile.in",
                     "gzip-1.3.14/doc/version.texi"]:
        try:
            info = t.getmember(filename)
        except KeyError:
            print("ERROR: Did not find {filename} in tar archive")
        else:
            print(f"{info.name} is {info.size:d} bytes")
gzip-1.3.14/doc/Makefile.in is 43510 bytes
gzip-1.3.14/doc/version.texi is 105 bytes

解压 tar 文件

可使用 extractfile() 方法读入压缩文档中某个成员的数据:

with tarfile.open(tar_file_path, "r") as t:
    for filename in ["gzip-1.3.14/doc/Makefile.in"]:
        try:
            # 解压其中某个文件为二进制数据
            f = t.extractfile(filename)
        except KeyError:
            print(f"ERROR: Did not find {filename} in tar archive")
        else:
            print(filename, ":")
            print(f.read().decode("utf-8")[0:100])
gzip-1.3.14/doc/Makefile.in :
# Makefile.in generated by automake 1.11a from Makefile.am.
# @configure_input@

# Copyright (C) 199

解压 tar 压缩文档中文件到硬盘

out_dir = os.path.join(FileDir, "tar")
with tarfile.open(tar_file_path, "r") as t:
    t.extract("gzip-1.3.14/doc/Makefile.in", out_dir)
print(os.listdir(out_dir))
['gzip-1.3.14']

解压整个 tar 压缩文档到硬盘:gzip-1.3.14.tar/gzip-1.3.14/..tar/gzip-1.3.14/..

标准库文档中有一个注释提到 extractall() 方法的安全性强于 extract() ,尤其适用于无法倒带读取输入的较早部分的流数据,所以大多数情况下应该使用 extractall()

out_dir = os.path.join(FileDir, "tar")
with tarfile.open(tar_file_path, "r") as t:
    t.extractall(out_dir)
print(os.listdir(out_dir))
['gzip-1.3.14']

extractall() 方法也可以只解压部分文件,这需要将待提取的文件名或者 TarInfo 元数据容器作为参数传递给 extractall()

with tarfile.open(tar_file_path, "r") as t:
    t.extractall(out_dir, members=[t.getmember("gzip-1.3.14/doc/Makefile.in")], )
print(os.listdir(out_dir))
['gzip-1.3.14']

处理其他压缩文件

除了正常的 tar 归档文件,tarfile 模块还可处理通过 gzip 或 bzip2 协议压缩的归档文件。要打开一个压缩的归档文件,根据不同的压缩协议,传入 ":gz"":bz2" 模式参数到open()函数。

# gzip-1.3.14.tar.gz/gzip-1.3.14.tar/gzip-1.3.14/..  →  gz/gzip-1.3.14/..
gz_file_path = os.path.join(FileDir, "gzip-1.3.14.tar.gz")
out_dir = os.path.join(FileDir, "gz")
# 直接解压 tar.gz 文件
with tarfile.open(gz_file_path, mode="r:gz") as out:
    out.extractall(out_dir)
print(os.listdir(out_dir))
['gzip-1.3.14']
# gzip.bzip2/gzip/..  →  gzip/..
bz2_file_path = os.path.join(FileDir, "gzip.bz2")
out_dir = os.path.join(FileDir, "gzip")
# 直接解压 .bz2 文件
with tarfile.open(bz2_file_path, mode="r:bz2") as out:
    out.extractall(out_dir)
print(os.listdir(out_dir))
['.tarball-version', 'aclocal.m4', 'algorithm.doc', 'amiga', 'atari',
'AUTHORS', 'bits.c', 'build-aux', 'ChangeLog', 'ChangeLog-2007',
'configure', 'configure.ac', 'COPYING', 'crypt.c', 'crypt.h',
'deflate.c', 'doc', 'GNUmakefile', 'gunzip.1', 'gunzip.in', 'gzexe.1',
'gzexe.in', 'gzip.1', 'gzip.c', 'gzip.doc', 'gzip.h', 'inflate.c',
'INSTALL', 'lib', 'lzw.c', 'lzw.h', 'm4', 'maint.mk', 'Makefile.am',
'Makefile.in', 'msdos', 'NEWS', 'nt', 'os2', 'primos', 'README',
'revision.h', 'sample', 'tailor.h', 'tests', 'THANKS', 'TODO',
'trees.c', 'unlzh.c', 'unlzw.c', 'unpack.c', 'unzip.c', 'util.c',
'vms', 'zcat.1', 'zcat.in', 'zcmp.1', 'zcmp.in', 'zdiff.1',
'zdiff.in', 'zegrep.in', 'zfgrep.in', 'zforce.1', 'zforce.in',
'zgrep.1', 'zgrep.in', 'zip.c', 'zless.1', 'zless.in', 'zmore.1',
'zmore.in', 'znew.1', 'znew.in']

bz2

解压 bz2 文件

import bz2

bz2_file_path = os.path.join(FileDir, "gzip.bz2")
out_dir = os.path.join(FileDir, "gzip")
# gzip.bzip2/gzip/..  →  gzip
with bz2.open(bz2_file_path, mode="rb") as out:
    with open(out_dir, "wb") as f:
        f.write(out.read())

zipfile

解压 zip 文件

import zipfile

zip_file_path = os.path.join(FileDir, "gzip.zip")
out_dir = os.path.join(FileDir, "zip")
# gzip.zip/..  →  zip/..
with zipfile.ZipFile(zip_file_path) as zf:
    zf.extractall(out_dir)