HN 日本語サマリー

← 一覧へ戻る
プログラミング

.tar.gzファイルをcatコマンドで結合できないのはなぜか?

Why can't you combine .tar.gz files with cat? (alexwlchan.net)

8 pointsby surprisetalk10 コメント

要約

この記事は、複数の.tar.gzファイルを結合しようとした際にcatコマンドが機能しない理由を解説しています。tarとgzipの内部構造と動作原理を説明し、なぜ単純なバイト列の連結が失敗するのか、そして安全に結合するための正しい方法(Pythonのtarfileモジュールを使用)を示しています。

全文翻訳

なぜ.tar.gzファイルをcatで結合できないのか? 2026年8月20日投稿 複数の.tar.gzアーカイブを生成するプロジェクトに取り組んでおり、それらを1つの最終ファイルに結合する必要があります。単純にバイトを連結すればよいと思ったのですが、うまくいきませんでした。この一見簡単なタスクは、tarとgzipに対する私の誤った理解を露呈しました。 コードを修正するために、まず私のメンタルモデルを修正する必要がありました。それは、テープドライブ、特許法、そしてファイルの終わりマーカーへと私を導きました。 tarはテープアーカイバの略 tarは、複数のファイルとそのメタデータ(ファイル名、タイムスタンプ、ディレクトリ構造)を1つのファイルにまとめるファイルアーカイバです。 元々は磁気テープ用に設計されており、ファイル構造は、その媒体の物理的制約によって形成されています。 シーケンシャルリード。磁気テープは、最初から始めてテープの最後まで再生するときに最も効率的です。 追記のみの書き込み。初期のテープは、既存のデータを上書きするのではなく、レコードの末尾にデータを追加することしかできませんでした。 固定データサイズ。テープには固定容量があり、初期のテープには固定データブロックサイズがありました。 内部的には、tarアーカイブは、固定サイズのブロックに分割されたファイルのシーケンスです。ファイルにはヘッダーブロック(ファイル名やファイルサイズなどのメタデータを含む)とデータブロック(ファイルの内容)があります。ファイルの後に、すべてゼロで満たされた2つ以上のブロックがあります。これらは、リーダーにアーカイブ内のそれ以降のすべてを無視するように指示するファイルの終わり(EOF)マーカーを形成します。 tarアーカイブの内部構造を示すアーキテクチャ図。ヘッダーとデータブロックを持つ2つのファイル、2つのゼロブロック、そして2つの無視されたブロックがあります。 ヘッダー データ データ ヘッダー データ データ ゼロ ゼロ 無視 無視 ファイル1 ファイル2 EOFマーカー この構造は物理テープを反映しています。ファイルをシーケンシャルに読み取ったり、新しいファイルを末尾に追加したりできます。そのシーケンシャルな設計が、tarがネットワーク経由でのストリーミングに人気がある理由です。完全なアーカイブをダウンロードするのを待たずに、受信したファイルをすぐに処理できます。 この構造を知ることで、以前は混乱していたtarの側面を理解するのに役立ちます。 ファイルサイズは事前に宣言する必要があります。データブロックを書き込む前に、ヘッダーにファイルサイズを書き込む必要があります。PythonのTarFile.addfile APIを使用する際、tarinfo.sizeを設定し忘れることがよくあります。そのため、Pythonはヘッダーに0を書き込み、空のアーカイブを作成します。 アーカイブには重複するファイル名を含めることができます。テープ上の既存のブロックを編集または削除することはできないため、同じファイル名を持つ新しいバージョンを追記してファイルを更新します。アーカイブを展開すると、後のファイルが前のファイルを上書きします。 EOFマーカー以降はすべて無視されます。物理テープは固定容量を持っているため、EOFマーカーはデータの終了位置と空のテープの開始位置を示します。GNU tarのようなツールにはEOFマーカーを過ぎて読み取りを続けるための--ignore-zerosフラグがありますが、デフォルト設定で読み取れるアーカイブを作成したいと考えています。 tarアーカイブを単純に連結するアプローチを試しましたが、リーダーが最初のEOFマーカーで停止するため、失敗します。代わりに、Pythonのtarfileモジュールを使用してアーカイブを結合しています。各アーカイブを展開し、そのメンバーを新しいアーカイブにコピーします。この新しいアーカイブには、単一のEOFマーカーが含まれます。 import tarfile def combine_tars(output_file, input_files): """複数のtarアーカイブを単一のアーカイブに結合します。""" with tarfile.open(output_file, "w") as out: for f in input_files: with tarfile.open(f, "r") as src: for member in src.getmembers(): out.addfile(member, src.extractfile(member)) combine_tars("numbers.tar", ["one.tar", "two.tar", "three.tar"]) これは生のバイトを連結するよりも多くのコードですが、読み取るために特別な設定を必要としないtarアーカイブを作成します。 gzipは単一のストリームを圧縮します gzipは、単一のファイルまたはデータストリームを受け取り、それを小さくするストリーム圧縮ツールです。圧縮はロスレスなので、元のファイルを取得するために逆転させることができます。 tarとは異なり、gzipは特許法への対応として生まれました。gzipファイル形式を定義するRFC 1952を読むと、作成された時期を反映した3つの設計上の制約があります。 特許フリー。gzipツールは、当時特許で保護されていたLZWアルゴリズムの基盤を持つ圧縮ツールであるcompressのフリーソフトウェア代替として書かれました。 ストリーマブル。gzipファイルの圧縮または解凍は、少量で境界付けられたメモリ量のみを使用する必要があります。1990年代初頭、RAMが今日よりもはるかに希少で高価だった頃、小さく連続したチャンクでデータを処理できる能力は不可欠でした。 ポータブル。gzipファイルは、CPU、OS、ファイルシステム、およびそれが作成されたコンピューターのその他の側面から独立している必要があります。今日ではこのようなポータビリティを当然のことと考えていますが、常にそうであったわけではありません。 内部的には、gzipファイルは1つ以上の「メンバー」のシーケンスです。各メンバーにはヘッダー(元のファイル名や変更時刻などのメタデータを含む)、圧縮されたデータ、およびトレーラー(CRC32チェックサムと非圧縮サイズを含む)があります。ファイルは最後のトレーラーの後に終了します。gzipにはEOFマーカーはありません。 gzipファイルの内部を示すアーキテクチャ図。ヘッダー、データブロック、トレーラーを持つ3つのメンバーがそれぞれあります。 ヘッダー データ トレーラー ヘッダー データ トレーラー ヘッダー データ トレーラー メンバー1 メンバー2 メンバー3 概念的には、メンバーをファイルの類似体と見なしたくなりますが、gzipはそうではありません。ツールは複数のメンバーを同じデータストリームの一部として扱いますが、個別にリストしたり抽出したりすることはできません。マルチメンバーgzipファイルを解凍すると、単一のストリームが返されるだけです。 メンバーが次々と続き、EOFマーカーがないため、gzipファイルを連結するにはバイトを連結するだけで済みます。 echo "one uno eins" | gzip > one.gz echo "two duo zwei" | gzip > two.gz echo "three tres drei" | gzip > three.gz cat one.gz two.gz three.gz > numbers.gz gunzip --uncompress --to-stdout numbers.gz .tar.gzアーカイブを結合するにはどうすればよいですか? tarとgzipは固い友達です。tarはディレクトリツリーを単一のストリームに結合し、gzipはそのストリームを小さくします。両方ともシーケンシャルリードをサポートしているため、.tar.gzはネットワーク経由でデータをストリーミングするのに非常に人気があります。アーカイブ全体をダウンロードする前に個々のファイルの処理を開始できます。 私の間違いは、tar.gzファイルをcatで結合しようとしたことでした。gzipは協力しますが、tarは反抗します。 gzipは圧縮されたメンバーを単一のストリームにうまく結合しますが、tarが解凍されたストリームを読み取ろうとすると、最初のアーカイブのEOFマーカーに遭遇して読み取りを停止します。gzipは続行するのに満足していますが、tarは諦めています。 .tar.gzファイルを安全に結合するには、基になるメンバーを抽出し、新しいファイルに書き込む必要があります。これは、上記のPython関数をプレーンな読み取り/書き込み(r/w)からgzip圧縮された読み取り/書き込み(r:gz/w:gz)に変更することを意味します。 import tarfile def combine_tar_gzs(output_file, input_files): """複数のgzip圧縮tarアーカイブを単一のアーカイブに結合します。""" with tarfile.open(output_file, "w:gz") as out: for f in input_files: with tarfile.open(f, "r:gz") as src: for member in src.getmembers(): out.addfile(member, src.extractfile(member)) combine_tar_gzs("numbers.tar.gz", ["one.tar.gz", "two.tar.gz", "three.tar.gz"]) これは混乱するバグとして始まりましたが、楽しいサイドクエストになりました。今ではこれらのフォーマットがどのように機能するかを理解し、元のコードがなぜ機能しないのかを理解し、それをどのように修正できるかを理解しています。秘密のショートカットや明らかな最適化を見逃していないという知識を持って、プロジェクトに戻ることができます。