Go バイナリファイルの読み書き
今回は、Goで固定長と可変長なデータがあるバイナリファイルを読み書きしてみたいと思います。
やりたいこと
複数個ファイルを1つのバイナリファイルにする。(結合)
バイナリファイルを複数ファイルに書き戻す。(分割)
データ構造
下記のデータ構造を1つのファイルとして扱い、連結していく。
Offset: | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 |
---|---|---|---|---|---|---|---|---|
00 |
ファイル名サイズ (4 byte 固定: BigEndian) |
ファイルサイズ (4 byte 固定: BigEndian) |
||||||
08 |
ファイル名 (可変長: LittleEndian) |
|||||||
・・ |
ファイル内容 (可変長: LittleEndian) |
データ例
- ファイル名
HOGE.txt
- ファイルサイズ
- 8 byte
- ファイル内容
12345678
Offset: | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | データ値 | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
00 | 00 | 00 | 00 | 08 | 00 | 00 | 00 | 08 | 0x00000008 | 0x00000008 | ||||||
08 | 48 | 4F | 47 | 45 | 2E | 74 | 78 | 74 | H | O | G | E | . | t | x | t |
10 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
コマンド
↓ 2つのファイルを1つのファイルにまとめる
結合時
$ sample.exe join text.txt text.md
joined.dat
↓ 1つのファイルから2つのファイルを作る
分割時
$ sample.exe split joined.dat
text.txt
text.md
ソースコード
そしてザクっと作ったものがこちら。
結合時のファイル名とかは、固定にしてます。
sample.go
package main
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
)
type head struct {
FileNameSize uint32
FileSize uint32
}
func main() {
// 引数が足りない
if len(os.Args) < 2 {
os.Exit(1)
}
// 引数1つ目はコマンドとして扱う
switch os.Args[1] {
case `join`:
dist := `joined.dat`
// 書き込み先のファイルを作成
wf, err := os.Create(dist)
if err != nil {
log.Fatal(err)
}
defer wf.Close()
for _, file := range os.Args[2:] {
b, err := ioutil.ReadFile(file)
if err != nil {
log.Fatal(err)
}
// ファイルパスをファイル名のみに変更
filename := filepath.Base(file)
head := head{
FileNameSize: uint32(len(filename)),
FileSize: uint32(len(b)),
}
// 固定長部分を書き込み
err = binary.Write(wf, binary.BigEndian, &head)
if err != nil {
log.Fatal(err)
}
// データ部分を書き込み
_, err = wf.Write(append([]byte(filename), b...))
if err != nil {
log.Fatal(err)
}
}
fmt.Println(dist)
case `split`:
for _, file := range os.Args[2:] {
b, err := ioutil.ReadFile(file)
if err != nil {
log.Fatal(err)
}
buf := bytes.NewReader(b)
for {
// 固定長部分を読み取る
head := head{}
err := binary.Read(buf, binary.BigEndian, &head)
if err == io.EOF {
break
} else if err != nil {
log.Fatal(err)
}
// ファイル名を読み取る
filename := make([]byte, head.FileNameSize)
err = binary.Read(buf, binary.LittleEndian, &filename)
if err != nil {
log.Fatal(err)
}
// データを読み取る
data := make([]byte, head.FileSize)
err = binary.Read(buf, binary.LittleEndian, &data)
if err != nil {
log.Fatal(err)
}
// ファイルに出力する
if err := ioutil.WriteFile(string(filename), data, 0666); err != nil {
log.Fatal(err)
}
fmt.Println(string(filename))
}
}
default:
// コマンドが違う
os.Exit(1)
}
}
まとめ
固定長のところは、構造体を作って読み書き。
可変長部分のところは、
書くときは 何も考えずつなげて 吐き出す。
読むときは データ長のスライスを作って 読み込む。
という感じでコードを書いてみました。
良い書き方があったら教えてくださいネ~。