概要(tarfile は「フォルダを丸ごと束ねて圧縮・展開」できる標準機能)
tarfile は .tar、.tar.gz、.tar.bz2、.tar.xz などのアーカイブを作成・追記・一覧・解凍できる標準ライブラリです。zipfile と違い「フォルダ構造をそのまま束ねる」のが得意で、圧縮方式はモードで指定します。重要なのは「モードの選び方(w:gz など)」「相対パス化で再現性を高める」「安全な展開(パストラバーサル対策)」の3点です。with 文で開閉を自動化し、pathlib と組み合わせると安全で読みやすいコードになります。
基本の操作(ここが重要)
新規作成(圧縮あり)とファイル追加
import tarfile
# gzip 圧縮の tar.gz を新規作成
with tarfile.open("archive.tar.gz", mode="w:gz") as tar:
# ファイルを追加(アーカイブ内の名前は自動で元パス)
tar.add("project/README.md")
tar.add("project/src/app.py")
Python「w:gz」が gzip 圧縮(.tar.gz)、「w:bz2」が bzip2(.tar.bz2)、「w:xz」が xz(.tar.xz)、「w」は無圧縮(.tar)です。拡張子とモードを一致させると、後で扱う側が迷いません。
追記・読み込み・一覧取得
import tarfile
# 既存アーカイブへ追記
with tarfile.open("archive.tar.gz", mode="a:gz") as tar:
tar.add("project/config.yaml")
# 読み込みと一覧
with tarfile.open("archive.tar.gz", mode="r:gz") as tar:
print([m.name for m in tar.getmembers()]) # ファイル名のリスト
Python追記では同名が重複し得るため、アーカイブ内の配置規則(サブフォルダ、ユニーク名)を事前に決めておくと混乱しません。
展開(解凍)
from pathlib import Path
import tarfile
out = Path("extracted")
out.mkdir(parents=True, exist_ok=True)
with tarfile.open("archive.tar.gz", "r:gz") as tar:
tar.extractall(out)
Pythonextractall は「中身をすべて書き出す」動作です。安全対策は後述します。
圧縮とパス設計(方式・相対化・メタデータ)
圧縮方式の選び方
- テキスト中心でサイズを小さくしたいなら xz(w:xz)や bz2(w:bz2)が効きますが遅めです。
- 一般的でバランスが良いのは gzip(w:gz)。
- ビルド成果物やバイナリ中心で速度重視なら無圧縮(w)も選択肢です。
圧縮率と速度はトレードオフです。用途(配布、バックアップ、CIのキャッシュなど)で選びましょう。
相対パスで格納して再現性を高める
from pathlib import Path
import tarfile
root = Path("project")
with tarfile.open("project.tar.gz", "w:gz") as tar:
for p in root.rglob("*"):
if p.is_file():
# ルートからの相対パスで格納(絶対や環境依存を避ける)
tar.add(p, arcname=p.relative_to(root).as_posix())
Pythonarcname を相対化すると、どこで作っても展開後の構成が同じになり、再現性(deterministic builds)が上がります。
メタデータ(パーミッション・時刻)の扱い
tar は UNIX 系のパーミッションやタイムスタンプを保持します。格納時に TarInfo を使えば、属性を明示設定できます。
import tarfile, io, time
with tarfile.open("conf.tar", "w") as tar:
data = io.BytesIO(b"hello\n")
info = tarfile.TarInfo("config/hello.txt")
info.size = len(data.getvalue())
info.mtime = int(time.time())
info.mode = 0o644
tar.addfile(info, data)
Python安全な解凍(パストラバーサル対策と部分抽出)
ZIP/TAR スリップ対策(意図外の場所へ書き出さない)
アーカイブ内の名前に「../」が紛れていると、展開先の外へ書き出される恐れがあります。展開先ディレクトリ配下に限定するガードを入れます。
from pathlib import Path
import tarfile
def safe_extract(tar_path, outdir):
outdir = Path(outdir).resolve()
with tarfile.open(tar_path, "r:*") as tar:
for m in tar.getmembers():
dest = (outdir / m.name).resolve()
if outdir not in dest.parents and dest != outdir:
raise ValueError(f"危険なパス: {m.name}")
tar.extractall(outdir)
safe_extract("archive.tar.gz", "extracted")
Python「r:*」は圧縮方式を自動判別します。Python 3.12 以降は filter 引数で安全に寄せられます(例:filter=”data”)が、バージョンを跨ぐなら自前ガードが安心です。
特定ファイルだけ取り出す・内容を直接読む
import tarfile
with tarfile.open("archive.tar.gz", "r:gz") as tar:
tar.extract("config/app.yaml", path="out")
f = tar.extractfile("README.md") # ExFileObject を取得
content = f.read().decode("utf-8")
print(content)
Pythonextractfile なら「ディスクへ書き出さず中身を読み取る」ことができます。メモリ処理やストリーム応答に向きます。
実務パターン(フォルダ丸ごと圧縮・除外・配布・インメモリ)
フォルダ丸ごと圧縮(除外ルール付き)
from pathlib import Path
import tarfile
root = Path("src")
exclude_dirs = {".git", "__pycache__"}
with tarfile.open("src.tar.xz", "w:xz") as tar:
for p in root.rglob("*"):
if any(part in exclude_dirs for part in p.parts):
continue
if p.is_file():
tar.add(p, arcname=p.relative_to(root).as_posix())
Python不要フォルダを除外して、配布用の軽量アーカイブを作れます。
必要ファイルだけ選んで「ルート直下」に平坦化
import tarfile
from pathlib import Path
files = [Path("README.md"), Path("LICENSE"), Path("dist/app")]
with tarfile.open("release.tar.gz", "w:gz") as tar:
for f in files:
tar.add(f, arcname=f.name) # ルート直下に格納
Python展開先が迷わない構成にすると、利用者が楽になります。
インメモリで tar を生成(HTTP レスポンス向け)
import tarfile
from io import BytesIO
buf = BytesIO()
with tarfile.open(fileobj=buf, mode="w:gz") as tar:
tarinfo = tarfile.TarInfo("hello.txt")
data = b"world\n"
tarinfo.size = len(data)
tar.addfile(tarinfo, BytesIO(data))
payload = buf.getvalue() # bytes をそのまま送信・保存
Pythonディスクを使わず、その場でアーカイブを作れます。
よくある落とし穴の回避(モード・拡張子・巨大サイズ・権限)
モードと拡張子の不一致を避ける
「w:gz なのに .tar 」「w:xz なのに .tar.gz」といった不一致は後工程で混乱します。拡張子とモードを合わせるのを習慣化しましょう。
絶対パスや環境依存のパスを格納しない
arcname に絶対パスを入れると、展開側で予期せぬ場所へ書き出される原因になります。必ず相対化してから格納します。
大量・巨大ファイルの処理時間とメモリ
xz/bz2 は高圧縮ですが遅いので、ビルドやCIでは gzip へ寄せる、分割アーカイブを検討するなど「時間とサイズのバランス」を意識します。ストリーム読み書き(extractfile・addfile)でメモリ節約が可能です。
権限・所有者情報の復元
UNIX 系の権限や所有者情報が復元される場合があります。展開先環境で意図しない属性にならないよう、必要なら展開後に chmod/chown を調整します(特に配布用途)。
例題で身につける(定番から一歩先まで)
例題1:プロジェクトを相対パスで再現性高く圧縮
from pathlib import Path
import tarfile
root = Path("project")
with tarfile.open("project.tar.gz", "w:gz") as tar:
for p in root.rglob("*"):
if p.is_file():
tar.add(p, arcname=p.relative_to(root).as_posix())
Python例題2:安全展開(配下限定ガード付き)
from pathlib import Path
import tarfile
def safe_extract(tar_path, outdir):
outdir = Path(outdir).resolve()
with tarfile.open(tar_path, "r:*") as tar:
for m in tar.getmembers():
dest = (outdir / m.name).resolve()
if outdir not in dest.parents and dest != outdir:
raise ValueError(f"危険なパス: {m.name}")
tar.extractall(outdir)
safe_extract("project.tar.gz", "out")
Python例題3:メモリから文字列ファイルを追加して作成
import tarfile, io
with tarfile.open("conf.tar", "w") as tar:
content = io.BytesIO(b"key=value\n")
info = tarfile.TarInfo("config/settings.ini")
info.size = len(content.getvalue())
tar.addfile(info, content)
Python例題4:アーカイブ内テキストを直接読み出す
import tarfile
with tarfile.open("project.tar.gz", "r:gz") as tar:
f = tar.extractfile("README.md")
print(f.read().decode("utf-8"))
Pythonまとめ
tarfile は「フォルダ構造を保ったまま束ねる」場面で強力です。圧縮方式とモード(w:gz、w:xz など)を正しく選び、arcname を相対化して再現性を確保する。展開はパストラバーサル対策で出力先配下に限定し、必要に応じてメタデータを調整。with 文と pathlib の併用で、結合→安全確認→圧縮/展開の流れを明示すれば、配布・バックアップ・CI のキャッシュまで、短く安全なアーカイブ処理が書けます。
