tarfile.open()打开tar.gz文件必须显式指定mode='r:gz',否则报oserror;解压前须用os.path.normpath和isabs校验路径防穿越,extractfile()可安全读取单个文件。

tarfile.open() 打开 tar.gz 文件必须指定 mode='r:gz'
不加 mode 或写错模式(比如只写 'r')会导致报错 OSError: Not a gzipped file,哪怕文件后缀是 .tar.gz。Python 的 tarfile 不会自动根据后缀推断压缩方式,必须显式声明。
常见错误是直接 tarfile.open('a.tar.gz'),结果在读取时才崩;正确做法是:
import tarfile
with tarfile.open('archive.tar.gz', mode='r:gz') as tf:
tf.extractall()
-
'r:gz'表示以 gzip 方式读取 tar 流;'r:bz2'和'r:xz'同理 - 如果用
'r',它只尝试 raw tar,遇到 gzip 头就失败 - Windows 上路径含中文时,
extractall()可能解出乱码名,需额外处理encoding参数
extractall() 默认解压到当前目录,路径穿越风险要手动过滤
tarfile 不做路径安全检查,恶意包里含 ../../../etc/passwd 这种路径会真实写入系统关键位置。生产环境必须校验成员路径。
推荐在 extractall() 前先遍历 getmembers(),过滤掉非相对路径:
import tarfile
with tarfile.open('malicious.tar.gz', 'r:gz') as tf:
# 只保留合法的相对路径成员
safe_members = [m for m in tf.getmembers()
if not m.name.startswith(('/', '..')) and '../' not in m.name]
tf.extractall(members=safe_members)
- 仅靠
startswith('..')不够,因为'foo/../etc/shadow'也能绕过 - 更稳妥的是用
os.path.normpath()+os.path.isabs()双重判断 - 若目标目录已存在同名文件,
extractall()会直接覆盖,无提示
想提取单个文件?别用 extractall(),用 extractfile() 配合 read()
如果只需要包里某个配置文件的内容(比如 config.json),全量解压再读太重,还可能触发路径穿越或磁盘爆满。
直接从 tar 流中定位并读取:
import tarfile
with tarfile.open('app.tar.gz', 'r:gz') as tf:
try:
f = tf.extractfile('config.json') # 注意:传入的是内部路径字符串
if f:
content = f.read().decode('utf-8')
except KeyError:
print("config.json not found")
-
extractfile()返回BytesIO类对象,不是文件路径 - 传入的路径必须和
tf.getnames()列出的一致,区分大小写、斜杠方向 - 如果文件很大,
f.read()会一次性加载进内存,考虑用f.readline()或分块读
gzip 层报错 OSError: Not a gzipped file?先确认是不是真 gzip
有些文件后缀是 .tar.gz,但实际只是未压缩的 .tar(比如某些构建脚本误命名),或者用了别的压缩算法(如 zstd)。硬套 'r:gz' 必然失败。
- 用命令行快速验证:
file archive.tar.gz—— 输出含gzip compressed data才是真的 - 也可以试开:
tarfile.open(..., 'r:*')让 Python 自动探测,但兼容性差,某些旧版本不支持 - 若确定是其他格式,改用对应 mode:
'r:bz2'、'r:xz',或先用subprocess调7z/unlzma
事情说清了就结束。路径校验和压缩模式匹配,这两个点最容易漏,一漏就是线上事故。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











