Golang Cafe #11 まとめ archiveパッケージ
2014/01/05に開催された「Golang Cafe #11」についてのまとめです。
今回は私が気になったところを質問した後、archiveパッケージのtarパッケージとzipパッケージをみていきました。
サンプルコードは+TakashiYokoyama氏が準備してくれています。
私が質問した内容
先日まで写経+αを行っていた
- 作者: David Chisnall,デイビッド・チズナール,柴田芳樹
- 出版社/メーカー: ピアソン桐原
- 発売日: 2012/10/04
- メディア: 単行本(ソフトカバー)
- 購入: 1人 クリック: 5回
- この商品を含むブログを見る
package example type Public interface { Name() string } type Example struct { name string } //func (e Example) Nme() string { func (e Example) Name() string { return e.name } // func NewExample() Example { // return Example{"No Name"} // } func NewExample() Public { return Example{"No Name"} } func NewExample2() Public { e := Example{"No Name"} e.(Public) return e }
Go言語ではinterfaceに定義されるメソッドを自身の型に定義することでinterfaceを実装したことになるのですが、上記のコードでコメントアウトした行の様にメソッド名を間違っていた場合そのバグに気づくのに多大な時間を要する可能性があります。
それを防ぐために上記のような実装はいかがでしょう、ということらしいです。
1つ目の方法はNewExample関数のようにインスタンス生成時に戻り値をinterfaceにする方法です。
2つ目の方法はNewExample2関数のように生成したインスタンスに対してType assertionsを使用して確認しようというものです。
いずれもビルド時にエラーを発見しようというものですが、2つ目の方法はうまく行きません。
Type assertionsのページにも書かれてあるとおり、上記のコードでいうeがinterfaceでなくてはなりません。
正解はどのようなコードになるのか考えてみましが、関数の戻り値としてPublic型のinterfaceを返すのならNewExample関数で十分なのではないかと思います。
tarパッケージ
tarパッケージは所謂tarコマンドと同じようなことをすることができます。
圧縮
func main() { var file *os.File var err error if file, err = os.Create("output/sample.tar"); err != nil { panic(err) } defer file.Close() // Closeをしないと、展開できなくなる可能性がある。 // アーカイブユーティリティ(MacのFinderから)だとエラーで展開されない) tw := tar.NewWriter(file) defer tw.Close() var filepaths = []string { "files/b0044482_1413812.jpg", "files/dart_flight_school.png", "files/golang.txt", } for _, filepath := range filepaths { body := readFile(filepath) if body != nil { hdr := &tar.Header { Name: path.Base(filepath), // ディレクトリ階層を維持したままにする場合はそのまま // Name: filepath, Size: int64(len(body)), } if err := tw.WriteHeader(hdr); err != nil { println(err) } if _, err := tw.Write(body); err != nil { println(err) } } } }
手順は非常に簡単で、
- tar.NewWriterメソッドでWriterを作成します。引数にはio.WriterをとるのでファイルでなくてもWrite(p []byte) (n int, err error)なメソッドを実装しているものなら何でも構いません。
- それぞれのファイルを対象にHeaderをWriteHeaderメソッドで書き込みます。
- それぞれのファイルを対象にバイトデータをWriteメソッドで書き込みます。
だけです。
ディレクトリ階層を維持したままにするには、HeaderのNameを指定する際にディレクトリから指定するだけでOKです。
解凍
func main() { var file *os.File var err error if file, err = os.Open("output/sample.tar"); err != nil { log.Fatalln(err) } defer file.Close() // ReaderはClose()はない。 reader := tar.NewReader(file) var header *tar.Header for { header, err = reader.Next() if err == io.EOF { // ファイルの最後 break } if err != nil { log.Fatalln(err) } buf := new(bytes.Buffer) if _, err = io.Copy(buf, reader); err != nil { log.Fatalln(err) } if err = ioutil.WriteFile("output/" + header.Name, buf.Bytes(), 0755); err != nil { log.Fatal(err) } } }
- NewReaderメソッドでReaderを作成します。引数にはio.ReaderをとるのでファイルでなくてもRead(p []byte) (n int, err error)なメソッドを実装しているものなら何でも構いません。
- Nextメソッドでtarファイル内に含まれている各ファイルのヘッダを順次取得でき、またデータを順次処理していきます。
- Readメソッドで読み込むか、他のパッケージのメソッドに渡して終了です。
ただこのサンプルではディレクトリ階層を持つファイルを含んでいる場合は実行時エラーとなるので、
if err = ioutil.WriteFile("output/" + header.Name, buf.Bytes(), 0755); err != nil { log.Fatal(err) }
の部分を
s := "output/" + header.Name d, _ := filepath.Split(s) if _, err = os.Stat(d); err != nil { os.MkdirAll(d, 0755) } if err = ioutil.WriteFile(s, buf.Bytes(), 0755); err != nil { log.Fatal(err) }
のようにディレクトリが存在していない場合は作成してあげると正常に終了します。(エラー処理は適当です)
zipパッケージ
zipパッケージは所謂zipコマンドと同じようなことをすることができます。
圧縮
func main() { var file *os.File var err error if file, err = os.Create("output/sample.zip"); err != nil { log.Fatalln(err) } defer file.Close() zw := zip.NewWriter(file) var filepaths = []string { "files/b0044482_1413812.jpg", "files/dart_flight_school.png", "files/golang.txt", } var f io.Writer for _, filepath := range filepaths { body := readFile(filepath) if body != nil { // f, err = zw.Create(path.Base(filepath)) f, err = zw.Create(filepath) if err != nil { log.Fatal(err) } _, err = f.Write(body) if err != nil { log.Fatal(err) } } } // zip.WriterはClose時にエラーチェックをすること。 err = zw.Close() if err != nil { log.Fatal(err) } }
手順については先程のtarの場合と非常によく似ていますが、ファイルデータの書き出し部分が少し異なります。
解凍
func main() { reader, err := zip.OpenReader("output/sample.zip") if err != nil { log.Fatalln(err) } defer reader.Close() var rc io.ReadCloser for _, f := range reader.File { rc, err = f.Open() if err != nil { log.Fatal(err) } buf := new(bytes.Buffer) _, err = io.Copy(buf, rc) if err != nil { log.Fatal(err) } s := "output/" + f.Name d, _ := filepath.Split(s) if _, err = os.Stat(d); err != nil { os.MkdirAll(d, 0755) } if err = ioutil.WriteFile(s, buf.Bytes(), 0755); err != nil { log.Fatal(err) } rc.Close() } }
zipファイルを解凍する場合はtarの場合とは手順が少し異なりますが、簡単に行えます。
- 引数に解凍対象のファイルパスを指定してOpenReaderメソッドを実行します。戻り値にはReadCloserというものが返ります。
- ReadCloserは内部にReaderを持っており、そのFileフィールドにアクセスすることでzipファイルに含まれるファイルにアクセスすることができます。
- 個々のファイルのOpenメソッドを呼ぶとio.ReadCloserが取得できます。
- io.ReadCloserはio.Readerを実装しているので、適当なWrite系のメソッドに渡すことでファイルのデータを書き出します。
zip圧縮時にはパスワードを設定することができますが、Go言語の現在のバージョンではサポートされていないようです。