社内に perl で書かれている lltsv というコマンドラインツールがあって、それを golang に移植した github.com/sonots/lltsv というのを書いた。

こんなかんじで LTSV のキーを指定して特定キーだけに絞り込むやつ。

$ echo "foo:aaa\tbar:bbb\tbaz:ccc" | lltsv -k foo,bar -K
aaa   bbb

LTSV フォーマットは awk で扱おうとすると扱いづらいけど、lltsv があるとすっきり扱えるようになる

$ echo -e "time:2014-08-13T14:10:10Z\tstatus:200\ntime:2014-08-13T14:10:12Z\tstatus:500" \
  | lltsv -k time,status -K | awk '$2 == 500'
2014-08-13T14:10:12Z    500

速度比較してみたら perl 版の5倍ぐらい速くなってた。

勉強メモ

はじめての golang アプリケーションだったのでやりたいことを調べながら勉強しながら6時間ぐらいかけて書いた。

vimの設定

前に vim の設定をしたと思ってたんだけど、完全に掻き消えていたので mattn 氏の Vimを使ったGo言語開発手法 の記事を参考に vim の設定をした。

:Fmt で go fmt したり、:Import os とかできるようになって捗った。特に :Godoc パッケージ名 でドキュメントが見れるの大変便利。

ただ、パッケージは見れるんだけど基本の制御構文とかは見れないっぽい?ので、そっちはぐぐってた。

標準入力

bufio.NewScanner(os.Stdio) を使うといいようだ

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    line := scanner.Text()
    os.Stdout.WriteString(line)
}
if err := scanner.Err(); err != nil {
    os.Stderr.WriteString("reading standard input errored")
}

標準出力

fmt.Println(s) 

fmt パッケージは色々よきに計らってくれるそうだけど、その分遅いので、

os.Stdout.WriteSting(s) 

こっちを使うようにした。

配列への要素の追加

golang には正確には、配列に加えスライスという概念があるようだ。 配列が固定長で、スライスが可変長だと思っておけばよさそう。 Go 言語のスライスを理解しよう のスライドと Go のスライスの内部実装 の記事を読んだ。

スライスへの要素の追加は append でできるのだけど、ホイホイ追加するよりも最終的な長さがわかっている場合は、capacity サイズを指定して初期化しておくと、append の際に新しいオブジェクトを作るオーバヘッドを減らせるらしいので、cap をできるだけ指定するようにした。

slice := make([]string, 0, 10) // cap を指定しておく
slice = append(slice, "a")
slice = append(slice, "b")

関数ポインタ

Rubyで書いている時に Proc オブジェクトをあらかじめ作っておいて、ループの中の if 判定を省略する、とか自分はよくやるので同じことをやりたくなった。

golang でも関数オブジェクトを作れるようだ。無名関数を入れてもいいし、関数をそのまま入れても良い。

myFunc := func(a int, b int) int { a + b }

今回はこんな感じで関数オブジェクトを返す関数を作って利用した

func doMain() {
    funcAppend := getFuncAppend(no_key)
}

func getFuncAppend(no_key bool) func([]string, string, string) []string {
    if no_key {
        return func(selected []string, label string, value string) []string {
            return append(selected, value)
        }
    } else {
        return func(selected []string, label string, value string) []string {
            return append(selected, label+":"+value)
        }
    }
}

ANSI Color

端末への出力の場合に色をつけたかった。github.com/mgutz/ansi を利用できた。

ansi.Color(label, "green")+":"+ansi.Color(value, "magenta")

isatty

isatty 判定をして、STDOUT の出力先がパイプやリダイレクトの場合は色づけをなくしたかった。github.com/andrew-d/go-termutil を利用できた。

termutil.Isatty(os.Stdout.Fd())

エラー処理

os.Exit(1) でおもむろに終了させると defer に書いた関数終了時のファイルクローズ処理などがされずにいきなりアプリケーションが終了する、 という話をきいていたのでいきなり os.Exit(1) はしないようにした。代わりに

var exitCode = 0

とグルーバル変数を用意して、

if err != nil {
    os.Stderr.WriteString("failed to open and read " + filename)
    exitCode = 1
    return
}

というかんじで return で関数を終了して、大元の main 関数の本当に一番最後で

os.Exit(exitCode)

するようにした。うーん、グルーバル変数 ...

変数の型を調べる

ret := call("foo")

みたいに := を使える場合は変数の型を気にしなくて良いのだけど

var ret xxxx
if foo {
ret = call("foo")
} else {
ret =
call("bar")
}
みたいにしたい場合は := が使えないので xxxx に入る変数の型を調べる必要があるようだ。
 reflect を以下のようにして使うか、
import (
  "fmt"
  "reflect"
)
fmt.Println(reflect.typeOf(ret))

fmt.Printf の %T を使えばわかるようだ

import "fmt"
fmt.Printf("%T", ret)

クロスコンパイルとリリース

クロスコンパイルには gox を使うのが定番のようだ。

あとは、そうして作ったバイナリを github の releases ページに一括アップロードしてリリースするツールghr を @deeet 氏が作ってたのでそれを使ってリリースした。便利。

補足: ghr に 404 Not Found とだけ言われてアップロードに失敗することがある。そんなときはレポジトリを go get しなおして綺麗にすると大抵上手くいく。@todo: あとでコード読んで直す

ライセンス

Goとライセンス のスライドで言われている通り、実行ファイルを配布するにあたって、依存 package の LICENSE.txt も同梱しないといけない気がする(MIT License の場合。GPL だともっと辛みある)。バイナリ単体配布ができるのが golang の便利な所の1つなので zip に同梱するとかやりたくないし、go-bindata を使うなどして埋め込むとかするのがいいのだろうか??

なんらかのソリューションが求められる。