golang 標準パッケージ archive の紹介

# 前提知識

---

# アーカイブと圧縮の違い
アーカイブと圧縮はどちらもファイルやディレクトリを一つにまとめる手法ですが、それぞれ目的が異なります。

---

## アーカイブ
アーカイブは複数のファイルやディレクトリをまとめるために使用されます。
ただし、圧縮は行いません。

---

### Q. 圧縮せず、まとめるだけなんて使い道あるの？

---

### A. CUIで作業を行う場合は意外とあります。

例えば、`find` で再帰的にファイルを探索する場合、利用しなくなったディレクトリをアーカイブしておくと、探索対象が少なくなり、効率的に探索ができるようになります。

他には、ディレクトリの移動や転送を行う際、配下の構造が複雑だと、扱いが複雑になります。
ここでディレクトリをアーカイブすると、一つのファイルのように扱うことができ、移動や転送が簡単にできるようになります。

---

## 圧縮
圧縮はファイルを圧縮しサイズを小さくすることが目的です。
よく使われる形式として、zip, gzip, rar などがあります。

圧縮形式にはアーカイブ機能を含むものと、そうでないものがあります。

---

# tar と zip

---

## tar（tape archive）
tar は、UNIX系システムで一般的に使用されるアーカイブ形式です。
tarball などと呼ばれます。
※ "tar" + "ball"（ボールの意味）

tar にはファイルのパーミッションやオーナー、タイムスタンプ等のメタデータを保持する特徴があります。
これにより、ファイルをアーカイブおよび復元する際に、元の状態を維持することができます。

---

### tar の内部構造
tar は各ファイルやディレクトリに関する情報を記述したヘッダーと、ヘッダーに対応するファイルの内容のリストによって構成されています。

各ヘッダにはファイル名、パーミッション、所有者、グループ、サイズ、タイムスタンプなどのメタデータが記述されています。

---

## zip
zip は Windows を中心に様々なプラットフォームで使用される、とても一般的な圧縮形式です。
アーカイブと圧縮の両方をサポートしています。

パスワード設定や分割圧縮が可能で、セキュリティ面でのメリットもあります。

一方で、4GBを越えるファイルや圧縮後に2GBを越えるファイルは圧縮できないという特徴もあります。
現在はこの問題を解決した、zip64 という拡張フォーマットが所謂 zip として使用されています。

---

### zip の内部構造
zip は、セントラルディレクトリヘッダと各ファイルのローカルヘッダから構成されます。

セントラルディレクトリヘッダには zip ファイル全体の構造が記述されており、各ファイルのローカルヘッダーへのポインターとして機能します。
これにより、zip ファイル内のファイルの検索やアクセスが可能になります。

他にも、ファイルの圧縮方法、圧縮されたデータのサイズ、展開後のデータのサイズ、ファイルの属性、タイムスタンプなどの情報も含まれています。

ローカルヘッダーには、各ファイルに対応するファイル名、圧縮方法、圧縮されたデータのサイズ、展開後のデータのサイズ、CRCチェックサムなどの情報が記述されています。

---

## （余談） gzip について

gzip と zip は名前が似ていますが、これらは全くの別物です。
gzip にはアーカイブの機能はなく、ただの圧縮形式です。
よく tar（アーカイブ）→gzip（圧縮）のように組み合わせて使われます。

---

# 本題

---

# archive パッケージ
Go言語の標準パッケージ archive には、主にtarとzipを操作するためのパッケージが含まれています。

以下のような操作が可能です
* ファイルの追加
* 抽出
* 読み取り
* 書き込み

---

## tar 作成サンプル

```go
// 新しいtarアーカイブファイルを作成
tarFile, _ := os.Create("example.tar")
defer tarFile.Close()

// tarアーカイブのライターを作成
tarWriter := tar.NewWriter(tarFile)
defer tarWriter.Close()

// ソースファイル
srcFile, _ := os.Open("file.txt")
defer srcFile.Close()

// ファイルをtarアーカイブに追加
fileInfo, _ := srcFile.Stat()
header := &tar.Header{
    Name: fileInfo.Name(),
    Size: fileInfo.Size(),
}
tarWriter.WriteHeader(header)
io.Copy(tarWriter, srcFile)
```

---

## tar 展開サンプル

```go
// tarアーカイブファイルを開く
tarFile, _ := os.Open("example.tar")
defer tarFile.Close()

// tarアーカイブのリーダーを作成
tarReader := tar.NewReader(tarFile)

// tarアーカイブ内のファイルを展開
for {
    header, err := tarReader.Next()

    if err == io.EOF {
        break
    }

    if err != nil {
        panic(err)
    }

    outFile, _ := os.Create(header.Name)
    defer outFile.Close()

    io.Copy(outFile, tarReader)
}
```

---

## zip 作成サンプル

```go
// 新しいzipアーカイブファイルを作成
zipFile, _ := os.Create("example.zip")
defer zipFile.Close()

// zipアーカイブのライターを作成
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()

// ソースファイル
srcFile, _ := os.Open("file.txt")
defer srcFile.Close()

// ファイルをzipアーカイブに追加
fileInfo, _ := srcFile.Stat()
header, _ := zip.FileInfoHeader(fileInfo)
writer, _ := zipWriter.CreateHeader(header)
io.Copy(writer, srcFile)
```

---

## zip 展開サンプル

```go
// zipアーカイブファイルを開く
zipFile, _ := zip.OpenReader("example.zip")
defer zipFile.Close()

// zipアーカイブ内のファイルを展開
for _, file := range zipFile.File {
    outFile, _ := os.Create(file.Name)
    defer outFile.Close()

    fileReader, _ := file.Open()
    defer fileReader.Close()

    io.Copy(outFile, fileReader)
}
```

---

## zip 圧縮形式
`archive/zip` パッケージでは Deflate という圧縮アルゴリズムがデフォルトで使用されます（アルゴリズムの詳細は省略）。


---

`func (*Writer) RegisterCompressor` を使用することで、別の圧縮器を適用することもできます。

```go
// 新しいzipアーカイブファイルを作成
zipFile, err := os.Create("example.zip")
if err != nil {
    log.Fatal(err)
}
defer zipFile.Close()

// zipアーカイブのライターを作成
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()

// 圧縮アルゴリズムをBZIP2に変更
zipWriter.RegisterCompressor(zip.BZIP2, func(out io.Writer) (io.WriteCloser, error) {
    return bzip2.NewWriter(out), nil
})

// ファイルをzipアーカイブに追加
fileToArchive, err := os.Open("file.txt")
if err != nil {
    log.Fatal(err)
}
defer fileToArchive.Close()

// zipファイルにファイルを追加
fileInfo, err := fileToArchive.Stat()
if err != nil {
    log.Fatal(err)
}
header, err := zip.FileInfoHeader(fileInfo)
if err != nil {
    log.Fatal(err)
}
writer, err := zipWriter.CreateHeader(header)
if err != nil {
    log.Fatal(err)
}
if _, err := io.Copy(writer, fileToArchive); err != nil {
    log.Fatal(err)
}
```

---

## archive/zip で出来ないこと
現状(go1.22.0)はパスワード付きのZIPファイルを解凍したり、圧縮時にパスワードをかけたりすることが出来ません。
パスワード付きの zip を扱う必要がある場合はサードパーティ製のライブラリを使う必要があります。
（参考）
* https://github.com/alexmullins/zip
* https://github.com/yeka/zip

また、ファイルの分割圧縮も機能として用意はされていません。