Python tarfile在本地与Docker环境提取目录行为不一致排查
问题分析与解决方案
问题描述
在本地Ubuntu 22.04 + Python 3.9.15环境中,使用tarfile模块提取tar包中的目录a/时,会连带提取目录内的文件;但在Docker容器(Ubuntu 18.04 + Python 3.10.9)中执行相同代码,仅得到空目录。两种环境下tar.getnames()均显示tar包包含a/和a/test_file.py。
根因分析
- 代码逻辑缺陷:解压操作前未删除已存在的
a/目录,导致原有目录中的文件与解压操作产生干扰。 - Python版本行为差异:
- Python 3.9的
tarfile.extractall()提取已存在的目录成员时,不会修改原有目录及其内容,因此原有文件得以保留。 - Python 3.10的
tarfile.extractall()提取已存在的目录成员时,会重建该目录(删除原有内容),最终得到空目录。
- Python 3.9的
解决方案
方案1:解压前删除原有目录
在执行解压操作前,删除已存在的a/目录,确保从tar包中提取的内容是全新的:
import logging import os from pathlib import Path import tarfile import tempfile import shutil # 新增导入 def main(): with tempfile.TemporaryDirectory() as temp_dir: # make a tar file with one directory that contains one file file_path = Path(temp_dir) / "file.tar.gz" os.chdir(temp_dir) logging.info("Changed dir to %s", os.getcwd()) subdir = "a" os.mkdir(subdir) with open(Path(subdir) / "test_file.py", "w") as file_obj: file_obj.write("import this\n") with tarfile.open(file_path, "w:gz") as tar: tar.add(subdir, recursive=True) # 解压前删除原有目录 shutil.rmtree(subdir) # unpack tar file with tarfile.open(file_path) as tar: file_names = tar.getnames() logging.info(file_names) members = [tar.getmember(subdir)] tar.extractall(path=temp_dir, members=members) # check contents of extracted directory dir_path = Path(temp_dir) / subdir output = os.listdir(dir_path) logging.info("%s contents: %s", dir_path, output)
方案2:提取目录及其所有内容
修改代码,提取tar包中属于a/目录的所有成员(包括目录本身和内部文件),而不仅仅是目录成员:
import logging import os from pathlib import Path import tarfile import tempfile def main(): with tempfile.TemporaryDirectory() as temp_dir: # make a tar file with one directory that contains one file file_path = Path(temp_dir) / "file.tar.gz" os.chdir(temp_dir) logging.info("Changed dir to %s", os.getcwd()) subdir = "a" os.mkdir(subdir) with open(Path(subdir) / "test_file.py", "w") as file_obj: file_obj.write("import this\n") with tarfile.open(file_path, "w:gz") as tar: tar.add(subdir, recursive=True) # unpack tar file with tarfile.open(file_path) as tar: file_names = tar.getnames() logging.info(file_names) # 提取目录及其所有子成员 members = [member for member in tar.getmembers() if member.name == subdir or member.name.startswith(f"{subdir}/")] tar.extractall(path=temp_dir, members=members) # check contents of extracted directory dir_path = Path(temp_dir) / subdir output = os.listdir(dir_path) logging.info("%s contents: %s", dir_path, output)
说明
两种方案均可解决跨环境的行为差异问题:
- 方案1通过清理原有目录,确保解压操作不受前置操作影响。
- 方案2通过明确提取所有相关成员,避免因目录存在状态导致的行为差异。
内容的提问来源于stack exchange,提问作者Cheryl Danner
相关产品推荐
相关产品推荐

