write ahead log

ロールフォワード用

golangのimport文で別名つける

golangのimport文は別名をつけることができる.

普通のimport文は

package main

import (
    "fmt" //ここ
)

func main() {
    fmt.Println("hello")
}

こんな感じで文字列でパッケージ名を指定するだけ.

別名をつける場合には以下のようになる.

package main

import (
    f "fmt" //ここ
)

func main() {
    f.Println("hello")
}

ここまでは知ってたんだけど, パッケージ名指定なしにもできるらしい.

package main

import (
    . "fmt" //ここ
)

func main() {
    Println("hello")
}

.(ドット)ね.あーなるほどね.

_(アンダースコア)の方はdatabase/sqlで割と使う事が多いんじゃないかと思う.

宣言したファイルでは使わないけど, 依存パッケージでは使うよっていう宣言.

import (
    "database/sql" // SQL標準のアクセス用パッケージ
    _ "github.com/go-sql-driver/mysql"  // MySQLのドライバ(↑で使う)
)

地味なとこで知らないことがおおい...

golangでバイナリを操作する

golangは低レベルな処理(バイナリの取り扱いとかね)もちゃんと行うことができる.

基本(byte型を使う)

golangは組み込みでbyte型が用意されている.

これはその名の通りバイトを扱うための型で, 配列やスライスにすれば単純なバイトストリームを扱える.

package main

import (
    "fmt"
)

func main() {
    b := []byte{0xDe, 0xaD, 0xBe, 0xeF}
    fmt.Printf("%b\n", b) // 2進表示
    fmt.Printf("%d\n", b) // 10進表示
    fmt.Printf("%x\n", b) // 16進小文字表示
    fmt.Printf("%X\n", b) // 16進大文字表示
}

[実行結果]

[11011110 10101101 10111110 11101111]
[222 173 190 239]
deadbeef
DEADBEEF
bytesパッケージ

byteのスライスを扱う場面は多いので, 標準で操作するパッケージが用意されている.
(Package bytes)https://golang.org/pkg/bytes/

以下のように比較も簡単だし, 文字列操作を意識しているのかその手の関数が多い.
(byteのスライスはstringとしてみなせるし)

package main

import (
    "fmt"
    "bytes"
)

func main() {
    b := []byte{0xDe, 0xaD, 0xBe, 0xeF}
    b2 := []byte{0xDe, 0xaD, 0x00, 0x00}
    b3 := []byte{0xDe, 0xaD, 0xBe, 0xeF}

    c := bytes.Compare(b, b2) // => 1
    fmt.Println(c)
    c2 := bytes.Compare(b, b3) // => 0
    fmt.Println(c2)
}

[実行結果]

1
0
ファイルに書いてみる

ファイルはただのバイト列なので, ファイルにも書けますね.

package main

import (
    "io/ioutil"
)

func main() {
    b := []byte{0xDe, 0xaD, 0xBe, 0xeF}

    ioutil.WriteFile("test.bin", b, 0644)
}

[実行結果]

Endianの都合で逆になっちゃってますが...(Windowsはリトルエンディアン)

$ od -x test.bin
0000000 adde efbe
0000004
バッファを扱う(bytes.bufferを使う)

バイトストリームを操作する時にイチイチ操作関数を使っていると面倒でしょうがないです. io.Readerやio.Writerの操作メソッドを使いたいですね.
あと, いきなりファイルに書いたりせずにバッファリングしたい場面がほとんどです.

bytesパッケージにはbufferという構造体が用意されています.

標準入出力のテストにも使えます.

以下の様なパッケージがあるとして

// 挨拶するパッケージ
package hello

import (
    "fmt"
    "io"
)

func hello(out io.Writer) {
    fmt.Fprintf(out, "hello, world")
}

上記のテストコードは以下になります.
bufferへ書き出して比較するという感じです.
helloの引数にinterfaceを使ったおかげでいい感じになります.

package hello

import (
    "bytes"
    "testing"
)

func TestHello(t *testing.T) {
    expected := []byte("hello, world")

    stdout := new(bytes.Buffer)
    hello(stdout)

    if bytes.Compare(stdout.Bytes(), expected) != 0 {
        t.Fatal("greeting is not matched")
    }
}

このテストは無事通ります.

[実行結果]

$ go test
PASS
ok      _/C_/msys64/home/twinbird/test      0.128s
バイトストリームを構造体へ変換する(encoding/binaryを使う)

プログラムでデータを扱うとなるとやっぱり構造体でまとめたいですね.

一々バイトストリームから構造体へ変換する処理を書くのは面倒ですが,
golangにはいい感じにしてくれるパッケージがあります.

package main

import (
    "bytes"
    "fmt"
    "encoding/binary"
)

type Beef struct {
    One byte
    Two byte
    Three byte
    Four byte
}

func main() {
    b := []byte{0xDe, 0xaD, 0xBe, 0xeF}
    buf := bytes.NewBuffer(b)
    var beef Beef

    binary.Read(buf, binary.LittleEndian, &beef)
    fmt.Printf("%x\n", beef.One)
    fmt.Printf("%x\n", beef.Two)
    fmt.Printf("%x\n", beef.Three)
    fmt.Printf("%x\n", beef.Four)
}

[実行結果]

$ ./test.exe
de
ad
be
ef

読み出しだけ書いてみましたが, 書き出しもあります.

注意点としては

  • 構造体のメンバはちゃんとエクスポートしておかないとダメ
  • 渡すのは構造体へのポインタ

というところくらいだと思います.

エンディアンも指定出来て良いです.

golangでfnvハッシュ関数を使う

FNVハッシュ関数は64bit or 32bitでの出力を行うことができるハッシュ関数です.

SHAシリーズ(SHA-1など)やMDシリーズ(MD5など)などとは異なり, 衝突耐性よりも実装が効率的な事を重視しているっぽい.

逆にセキュリティなどを重視するところでは使っちゃダメですね.

サンプルコードをメモっとく.

package main

import (
    "fmt"
    "hash/fnv"
)

func hash32(message []byte) uint32 {
    h := fnv.New32()
    h.Write(message)
    sum := h.Sum32()
    return sum
}

func hash64(message []byte) uint64 {
    h := fnv.New64()
    h.Write(message)
    sum := h.Sum64()
    return sum
}

func main() {
    fmt.Println(hash32([]byte("Test Hashing")))
    fmt.Println(hash64([]byte("Test Hashing")))
}

golangでシグナルをハンドリングする

golangでコマンド作ってもctrl + cとかで止めたくなるじゃないですか.

ちょっと調べた.

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    sig_ch := make(chan os.Signal, 1)
    signal.Notify(sig_ch,
        os.Interrupt,
        syscall.SIGHUP,
        syscall.SIGINT,
        syscall.SIGTERM,
        syscall.SIGQUIT)

    exit_ch := make(chan int)
    go func() {
        for {
            s := <-sig_ch
            switch s {
            case os.Interrupt:
                // windowsはこれで頑張る
                fmt.Println("Interrupt")
                exit_ch <- 0
            case syscall.SIGHUP:
                fmt.Println("SIGHUP")
                exit_ch <- 0
            case syscall.SIGINT:
                fmt.Println("SIGINT")
                exit_ch <- 0
            case syscall.SIGTERM:
                fmt.Println("SIGTERM")
                exit_ch <- 0
            case syscall.SIGQUIT:
                fmt.Println("SIGQUIT")
                exit_ch <- 0
            default:
                fmt.Println("Other")
                exit_ch <- 1
            }
        }
    }()
    code := <-exit_ch
    os.Exit(code)
}

Windowsはちょっと別枠ですが, ちゃんと取り扱えるのはいいですね.

余談ですが, Windows + msys2だとうまく動かないのでいい方法が知りたいです.

golangでtsv(csv)を読む

探せばいくらでも出てきそうだけど,メモしておく.

コード見たほうが早いと思うので.

package main

import (
    "encoding/csv"
    "fmt"
    "log"
    "strings"
)

func main() {
    // テスト用文字列
    str := "test\tテスト\nHello\tこんにちは"

    // CSVのReaderを用意
    r := csv.NewReader(strings.NewReader(str))

    // デリミタ(TSVなら\t, CSVなら,)設定
    r.Comma = '\t'

    // コメント設定(なんとコメント文字を指定できる!)
    r.Comment = '#'

    // 全部読みだす
    records, err := r.ReadAll()
    if err != nil {
        log.Fatal(err)
    }

    // 各行でループ
    for _, v := range records {
        // 1列目
        fmt.Print(v[0])

        fmt.Print(" | ")

        // 2列目
        fmt.Println(v[1])
    }
}

実行結果

$ ./tsv.exe
test | テスト
Hello | こんにちは

割と使いやすいライブラリと思う(クセがない)

vsftpdでディレクトリが削除できない

WindowsからFTPクライアントで(私の場合はエクスプローラで)ディレクトリを削除しようとすると以下のエラーが出た.

550 Create directory operation failed.

本当に原因がわからなくて30分くらい悩んだのだけど,どうも.付きのファイルがFTPで取得できていないのが問題らしい.

そこで/etc/vsftpd/vsftpd.conf内に以下を追記してサービス再起動で解決した.

force_dot_files=YES

マジでハマった.

golangでオブジェクトをシリアライズしたい

cacheに乗せたい時やちょっとしたものを作るときには簡単にシリアライズ/デシリアライズできる パッケージがあると便利だ.

gobパッケージを使うと簡単にできるようだ.
rpc向けのパッケージらしい.

他ではencoding/jsonやencoding/xmlだろうか.ここら辺はググればすぐ出ると思う. encoding/binaryも使い道によっては良いと思う.

それでもgobを使ってみたのはgobの方が早いとの記述を何処かで見たからだ.(が,測定はしていない)

使い方は絶対忘れるので作ったサンプルを書いておく.

package main

import (
    "bytes"
    "time"
    "fmt"
    "encoding/gob"
)

// サンプル用の構造体
// 何となくブログ風味
type Entry struct {
    ID         int64 `datastore:"-"`
    Title      string
    Contents   string
    CreateAt   time.Time
    UpdateAt   time.Time
    DeleteAt   time.Time
}

// シリアライズ用のメソッド
// レシーバ(e)の値をシリアライズしてbyte配列にする
func (e *Entry) GobEncode() ([]byte, error) {
    // エンコーダを作る.
    // io.Writerであれば何でも良いのでここではbytes.Bufferを使う
    w := new(bytes.Buffer)
    encoder := gob.NewEncoder(w)

    // 先頭から順番にエンコードしていく
    // 必ず先頭から1つずつエンコードを実行していく必要があるらしい

    if err := encoder.Encode(e.ID); err != nil {
        return nil, err
    }
    if err := encoder.Encode(e.Title); err != nil {
        return nil, err
    }
    if err := encoder.Encode(e.Contents); err != nil {
        return nil, err
    }
    if err := encoder.Encode(e.CreateAt); err != nil {
        return nil, err
    }
    if err := encoder.Encode(e.UpdateAt); err != nil {
        return nil, err
    }
    if err := encoder.Encode(e.DeleteAt); err != nil {
        return nil, err
    }
    return w.Bytes(), nil
}

// デシリアライズするためのデコーダ
// byte配列を渡してレシーバへ値を入れる
func (e *Entry) GobDecode(buf []byte) error {
    // デコーダを作る.
    // io.Readerであれば何でも良いのでここではbytes.Bufferを使う
    r := bytes.NewBuffer(buf)
    decoder := gob.NewDecoder(r)

    // 先頭から順番にデコードしていく
    // 必ず先頭から1つずつデコードを実行していく必要があるらしい

    if err := decoder.Decode(&e.ID); err != nil {
        return err
    }
    if err := decoder.Decode(&e.Title); err != nil {
        return err
    }
    if err := decoder.Decode(&e.Contents); err != nil {
        return err
    }
    if err := decoder.Decode(&e.CreateAt); err != nil {
        return err
    }
    if err := decoder.Decode(&e.UpdateAt); err != nil {
        return err
    }
    if err := decoder.Decode(&e.DeleteAt); err != nil {
        return err
    }
    return nil
}

func main() {
    // 記録するデータ
    e1 := &Entry{
        ID: 1,
        Title: "title",
        Contents: "contents",
        CreateAt: time.Now(),
        UpdateAt: time.Now(),
        DeleteAt: time.Now(),
    }
    // bにシリアライズして入れる
    b, err := e1.GobEncode()
    if err != nil {
        fmt.Println("Error occured.", err)
    }

    // デシリアライズするために用意
    e2 := &Entry{}

    // bからe2へデシリアライズ
    if err = e2.GobDecode(b); err != nil {
        fmt.Println("Error occured.", err)
    }

    // デシリアライズしたものを表示
    fmt.Println(e2.ID)
    fmt.Println(e2.Title)
    fmt.Println(e2.Contents)
    fmt.Println(e2.CreateAt)
    fmt.Println(e2.UpdateAt)
    fmt.Println(e2.DeleteAt)
}

コメントも書いたし,多分思い出せるだろう.