Gauche で ZIP を扱う

アーカイバとして ZIP は特に広く普及した形式のひとつだろう。 スクリプトから操作したいことはしばしばあり、私は Gauche で ZIP を生成する拡張パッケージを作っていた。

https://github.com/SaitoAtsushi/Gauche-zip-archive

ZIP を読む方の機能もつい最近になって追加した。 ZIP を読むことについて以前に記事にしたことがあるし、それ自体はそんなに難しいことというわけではないのだが、現実に使われている ZIP には様々な拡張があったり、ときには壊れていたりもするのでどこまで対応すべきか考えるのが面倒で放置していた。 最終的にはごく基本的なものだけ読めればよいと割り切ることにした。 少なくともこのライブラリで作ったアーカイブはこのライブラリで読めるようにということは想定している。 それ以上に高機能で堅牢なものが必要であれば適当なライブラリのバインディングを用意した方が簡単だろう。

さて、ライブラリにはドキュメントもつけているが、英語で細かいことを書く能力がないのでごく簡単なものでしかない。 ここでもう少し解説しておくことにする。 (インストール手順は省略する。)

スクリプトからライブラリの機能を使うためには当然だがライブラリを use しなければならない。 ライブラリの名前は zip-archive なので (use zip-archive) と書くことで準備できる。

ライブラリが含む手続き群はふたつに分けられる。 アーカイブを作るためのものと読むためのものである。 まず作る方から取り上げる。 アーカイブを作る基本的な手順は open-output-zip-archiveアーカイブをオープンし、 zip-add-entry でエントリを追加して zip-close で閉じるというものになる。 例としては以下のような使い方が基本となる。

#!/usr/bin/env gosh
(use zip-archive)
(use rfc.zlib)

(let ((za (open-output-zip-archive "test.zip")))
  (zip-add-entry za "1.txt" "number one.")
  (zip-add-entry za "2.txt" "number two.")
  (zip-add-entry za "3.txt" "number three." :compression-level Z_NO_COMPRESSION)
  (zip-close za))

圧縮レベルを指定することもでき、これは open-deflating-port で使える指定と同じである。 1 から 9 のいずれかの整数を指定するか、 rfc.zlib ライブラリで定義されている定数を用いる。

アーカイブのクローズを忘れると正常なアーカイブが作られないので、可能なら call-with-output-zip-archive を使うのが望ましい。

#!/usr/bin/env gosh
(use zip-archive)

(call-with-output-zip-archive "test2.zip"
  (lambda(za)
    (zip-add-entry za "one.txt" "number 1.")
    (zip-add-entry za "two.txt" "number 2.")
    (zip-add-entry za "three.txt" "number 3.")))

といった具合だ。

次に ZIP の読み込みについて取り上げる。 open-input-zip-archive で開いたアーカイブから zip-entries で取出したエントリ群 (エントリをリストにしたもの) から具体的な値を取出すのが基本的な手順になる。 たとえばアーカイブに含まれるエントリのファイル名を表示するのはこう書ける。

#!/usr/bin/env gosh
(use zip-archive)

(let ((za (open-input-zip-archive "test.zip")))
  (for-each (lambda(entry)
              (display (zip-entry-filename entry))
              (newline))
            (zip-entries za))
  (zip-close za))

この例ではエントリの名前を表示したが、 zip-entry-timestamp でタイムスタンプ、 zip-entry-datasize で展開後のデータの大きさ、 zip-entry-body でエントリに格納されている内容を表示できる。 (タイムスタンプとして格納されている値は標準時として解釈するようにしているが、現実にはローカルタイムの場合もあるようだ。)

これもアーカイブを作成するときと同様にクローズを忘れないよう call-with-input-zip-archive を使うのが望ましい。 また、 open-input-zip-archive が生成するオブジェクトはコレクションクラスを継承しているので、 gauche.collection モジュールで定義されている総称手続きにそのまま渡すことができる。 これらのことを利用して書き替えるとこうなる。

#!/usr/bin/env gosh
(use zip-archive)
(use gauche.collection)

(call-with-input-zip-archive "test.zip"
  (lambda(za)
    (for-each (lambda(entry)
                (display (zip-entry-filename entry))
                (newline))
              za)))

以上が Gauche-zip-archive パッケージの機能の全てである。

Document ID: 471a3123363ee8deb54e703682ad7958