読者です 読者をやめる 読者になる 読者になる

write ahead log

ロールフォワード用

goでコマンドラインツールを作る時にflagパッケージを使う

golang

go言語(golang)はマルチプラットフォームでバイナリを吐けるのでコマンドラインツールを作るにはちょうどいい言語だと思います.

標準パッケージも充実していて,オプションやメッセージを出力するパッケージが標準で準備されています.

当初ググると以下が人気だということだったんですが,僕は割と保守的なので標準パッケージ使います.

GitHub - tcnksm/gcli: The easy way to build Golang command-line application.

osパッケージを使う

一番基礎的な扱い方はOSパッケージを使う方法でしょう. Cを使ったことがある人なら馴染みある感じです.

サンプルソース

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Println(os.Args)    // => [./flag_sample arg1 arg2]
    fmt.Println(os.Args[0]) // => ./flag_sample
    fmt.Println(os.Args[1]) // => arg1
    fmt.Println(os.Args[2]) // => arg2
}

os.Argsにはプログラムのパス(index:0)と引数(index:1以降)が入ります.

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample arg1 arg2
[./flag_sample arg1 arg2]
./flag_sample
arg1
arg2

flagパッケージを使う

引数を扱う時にはCで言うところのgetoptsみたいなものが欲しくなると思います.
(といいつつ僕もほとんど使ったことがないですが)

そこでflagパッケージがあります.
色々面倒事を引き受けてくれる便利なものです.これが標準なんだからいいもんです.

flagパッケージで値を受け取るには2つの方法があります.

  1. 変数を宣言してflag.xxxVarを呼び出す
  2. flag.xxxを呼び出して戻り値を受け取る

(xxxはそれぞれ型名)

flag.xxxVarを使う場合
package main

import (
    "fmt"
    "flag"
)

func main() {
    /* 
    * 1. `force`という名前のフラグで
     * 2. デフォルト値は`false`.
    * 3. Usageとして`無理やり何かする`を表示
    */
    b := flag.Bool("force", false, "無理やり何かする") 

    /* 使う前にParseしないとならない. */
    flag.Parse()

    /* ポインタが返されるので間接参照のためにアスタリスクつける */
    fmt.Println(*b)
}

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample 
false # => デフォルトのfalseが表示される

twinbird@:~/go/src/flag_sample$ ./flag_sample force=true
false # => -か--をオプションとみなすのでデフォルトのfalseが表示される(force=trueは単なる第一引数になる)

twinbird@:~/go/src/flag_sample$ ./flag_sample -force=true
true # => -forceオプションをtrueにしたのでtrue

twinbird@:~/go/src/flag_sample$ ./flag_sample --force=true
true # => --もオプションとみなすのでtrueになる

twinbird@:~/go/src/flag_sample$ ./flag_sample ---force=true
bad flag syntax: ---force=true
Usage of ./flag_sample:
  -force
          無理やり何かする

# => ---はオプション扱いしない.
#    フラグ指定がおかしいのでUsageが表示される.
#    Usageは指定してあった`無理やり何かする`が表示される

twinbird@:~/go/src/flag_sample$ ./flag_sample -force true
true # => forceとtrueの間に=がなくても認識される

twinbird@twinbird-pc:~/Dropbox/go/src/flag_sample$ ./flag_sample -h
Usage of ./flag_sample:
  -force
          無理やり何かする

# => -hオプションをつけるとすべてのオプションのUsageを表示してくれる.
flag.xxxを使う場合

それほど変わらない.
変数を先に定義してアドレスを渡すか、ポインタを戻してもらうかの違い.
個人的にはこちらのほうが使い勝手がよい.

package main

import (
    "fmt"
    "flag"
)

func main() {
    var b bool
    flag.BoolVar(&b, "force", false, "無理やり何かする")
    flag.Parse()
    fmt.Println(b)
}
ヘルプ(Usage)を作る

-hでのUsageの説明文もカスタマイズすることができる.

package main

import (
    "fmt"
    "flag"
    "os"
)

func main() {
    var b bool
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "使い方:これこれこんな感じ.\n")
        flag.PrintDefaults() // => オプションの説明文を表示
    }
    flag.BoolVar(&b, "force", false, "無理やり何かする")
    flag.Parse()
    fmt.Println(b)
}

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample -h
使い方:これこれこんな感じ.
  -force
          無理やり何かする
引数の数を得る

NArg()で得ることができます.flag.Parse()の実行後出ないと出ません.

package main                                                                                                                     
 
import (
    "fmt"
    "flag"
)

func main() {
    var b bool
    flag.BoolVar(&b, "force", false, "無理やりやる")
    flag.Parse()
    fmt.Println(flag.NArg())
}

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample -force true arg2
2

-forceがtrueということで3つではなく2つとちゃんと認識してますね.

1つのオプションに2つの指定方法を与える

xxxVarを利用するなら単に同じ変数を与えてやればよい.

package main

import (
    "fmt"
    "flag"
)

func main() {
    var b bool
    flag.BoolVar(&b, "force", false, "無理やりやる")
    flag.BoolVar(&b, "f", false, "無理やりやる")
    flag.Parse()
    fmt.Println(b)
}

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample 
false
twinbird@:~/go/src/flag_sample$ ./flag_sample -f true
true
twinbird@:~/go/src/flag_sample$ ./flag_sample -force true
true
指定されたオプションを順に処理する

visitを利用すれば良いようです.
visitAllにすれば指定されていないオプションも対象にするようです.

package main

import (
    "fmt"
    "flag"
)

func main() {
    var b bool
    var s string
    var i int
    flag.BoolVar(&b, "force", false, "無理やりやる")
    flag.IntVar(&i, "times", 0, "繰り返す")
    flag.StringVar(&s, "category", "", "カテゴリー指定")
    flag.Parse()
    fmt.Println(b)
    fmt.Println(s)
    fmt.Println(i)

    visit_str := ""
    flag.Visit(func(f *flag.Flag) {
        visit_str += fmt.Sprintf("%s: %v\n", f.Name, f.Value)
    })
    fmt.Println(visit_str)


    visitAll_str := ""
    flag.VisitAll(func(f *flag.Flag) {
        visitAll_str += fmt.Sprintf("%s: %v\n", f.Name, f.Value)
    })
    fmt.Println(visitAll_str)
}

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample -times=10 -force=true
true

10
force: true # <- ここからVisit(指定されたものだけ対象にするのでcategoryが出ない)
times: 10

category:   # <- ここからVisitAll(指定されていなくても対象になるのでデフォルト値が出る)
force: true
times: 10
FlagSet

カスタマイズしたフラグを作ったりするのに使えそうです.
公式を元に調べて別記事にしないとダメですね.
今回使ってません.

まとめ

これだけ機能があれば標準でも十分な気がします.