Python で tar.gz アーカイブを作成・展開する(tarfile)
tar.gz(tarball)は Unix/Linux 環境で最も一般的なアーカイブ形式だ。複数のファイルやディレクトリを 1 つの tar ファイルにまとめ、gzip で圧縮する。Python では標準ライブラリの tarfile モジュールを使って、tar.gz アーカイブの作成と展開をどちらも行える。
アーカイブを作成する
tarfile.open() でアーカイブファイルを開き、add() でファイルやディレクトリを追加する。モードに "w:gz" を指定すると gzip 圧縮付きの tar ファイルが作成される。
import tarfile
with tarfile.open("archive.tar.gz", "w:gz") as tar:
tar.add("data/report.txt")
tar.add("data/config.json")
ディレクトリを丸ごと追加することもできる。
import tarfile
with tarfile.open("project.tar.gz", "w:gz") as tar:
tar.add("myproject")
この場合、myproject ディレクトリとその中身がすべてアーカイブに含まれる。
arcname でアーカイブ内のパスを変更する
add() をそのまま使うと、ファイルシステム上のパスがそのままアーカイブ内のパスになる。arcname パラメータを使えば、アーカイブ内での名前を自由に変更できる。
import tarfile
with tarfile.open("backup.tar.gz", "w:gz") as tar:
# 実際のパスは "data/2025/report.txt" だが
# アーカイブ内では "report.txt" として格納
tar.add("data/2025/report.txt", arcname="report.txt")
# ディレクトリのパスを短くする
tar.add("very/deep/nested/src", arcname="src")
ディレクトリを追加する際に arcname を指定すると、そのディレクトリ以下の相対パスはそのまま維持される。深いネストを避けたい場合に便利だ。
アーカイブを展開する
展開には "r:gz" モードで開いて extractall() を使う。
import tarfile
with tarfile.open("archive.tar.gz", "r:gz") as tar:
tar.extractall(path="./output")
extractall() の path 引数で展開先ディレクトリを指定できる。省略するとカレントディレクトリに展開される。
特定のファイルだけを展開したい場合は extract() を使う。
import tarfile
with tarfile.open("archive.tar.gz", "r:gz") as tar:
tar.extract("data/config.json", path="./output")
アーカイブの中身を確認する
展開せずにアーカイブの内容を一覧表示するには getnames() や getmembers() を使う。
import tarfile
with tarfile.open("archive.tar.gz", "r:gz") as tar:
# ファイル名の一覧
print(tar.getnames())
# 詳細情報(サイズ、更新日時など)
for member in tar.getmembers():
print(f"{member.name} {member.size} bytes "
f"{'dir' if member.isdir() else 'file'}")
getmembers() が返す TarInfo オブジェクトには、ファイルサイズ、パーミッション、更新日時などのメタデータが含まれている。展開前に中身を精査したい場面で役立つ。
圧縮方式の違い
tarfile モジュールは gzip 以外の圧縮方式にも対応している。モード文字列を変えるだけで切り替えられる。
| モード | 圧縮方式 | 拡張子 |
|---|---|---|
| w:gz | gzip | .tar.gz |
| w:bz2 | bzip2 | .tar.bz2 |
| w:xz | xz (LZMA) | .tar.xz |
| w | 無圧縮 | .tar |
読み込み時も同様に r:gz, r:bz2, r:xz を指定するが、r:* を使えば圧縮方式を自動判別してくれる。
import tarfile
# 圧縮方式を自動判別して開く
with tarfile.open("archive.tar.gz", "r:*") as tar:
tar.extractall(path="./output")
圧縮率と速度のトレードオフは方式によって異なる。
圧縮・展開速度が速く、互換性が最も高い。多くの環境で標準的にサポートされている
圧縮率が最も高いが、圧縮に時間がかかる。配布用アーカイブなどサイズを最小化したい場面に向いている
セキュリティ上の注意点
tarfile を使う際に最も気をつけるべきなのがパストラバーサル攻撃だ。悪意のあるアーカイブには ../../etc/passwd のような相対パスが含まれていることがあり、extractall() で展開すると意図しない場所にファイルが書き込まれる恐れがある。
Python 3.12 以降では、extractall() にフィルタ機能が追加された。
import tarfile
with tarfile.open("untrusted.tar.gz", "r:gz") as tar:
# Python 3.12 以降: data フィルタで安全に展開
tar.extractall(path="./output", filter="data")
filter="data" を指定すると、絶対パスや .. を含むパス、シンボリックリンクなど危険な要素が自動的に除外される。
Python 3.11 以前では、手動でメンバーを検証する必要がある。
import tarfile
import os
def safe_extract(tar, path="."):
for member in tar.getmembers():
member_path = os.path.join(path, member.name)
abs_path = os.path.realpath(member_path)
abs_target = os.path.realpath(path)
# 展開先がターゲットディレクトリ内か確認
if not abs_path.startswith(abs_target + os.sep):
raise Exception(
f"パストラバーサルの疑い: {member.name}"
)
tar.extractall(path=path)
with tarfile.open("untrusted.tar.gz", "r:gz") as tar:
safe_extract(tar, "./output")
アーカイブを開く
getmembers() で中身を検証
危険なパスがないか確認
安全な場合のみ extractall() で展開
大きなファイルをストリーミングで処理する
巨大なアーカイブを扱う場合、全体をメモリに載せずにストリーミングで処理したいことがある。extractfile() を使えば、個々のファイルをファイルオブジェクトとして取得できる。
import tarfile
with tarfile.open("large_archive.tar.gz", "r:gz") as tar:
for member in tar:
if member.isfile() and member.name.endswith(".csv"):
f = tar.extractfile(member)
if f is not None:
# 1 行ずつ読み込む(メモリ効率が良い)
for line in f:
process_line(line.decode("utf-8"))
extractfile() はディレクトリやシンボリックリンクに対しては None を返すため、None チェックを忘れないようにしたい。
既存のアーカイブにファイルを追加する
"a" モードで開くと既存のアーカイブにファイルを追加できるが、これは無圧縮の tar ファイルでのみ動作する。gzip 圧縮済みのアーカイブへの追加はサポートされていない。
import tarfile
# 無圧縮 tar への追加
with tarfile.open("archive.tar", "a") as tar:
tar.add("new_file.txt")
gzip 圧縮アーカイブにファイルを追加したい場合は、一度展開してから再度アーカイブを作成するか、一時ディレクトリを使って再構築する方法を取ることになる。この制限は gzip の圧縮方式に起因するもので、tarfile モジュール固有の問題ではない。