Golang Cafe #14 まとめ flagパッケージ

2014/01/26に開催された「Golang Cafe #14」についてのまとめです。
今回はflagパッケージ以下をみていきました。
os.Argsが単純にプログラム名と実行時引数の一覧を取得出来るのに対して、flagパッケージとは何かと簡単にいうと、「実行時の引数(オプション)をいい感じに処理してくれる」パッケージです。


+TakashiYokoyama氏が準備してくれたサンプルはこちらから、私の書いたサンプルはGitHubに置いてあります。

flagパッケージ

サンプル1
package main

import (
    "flag"
    "fmt"
    "strings"
)

func main() {
    x := flag.Bool("x", false, "xフラグです。true または false")
    y := flag.Bool("y", false, "yフラグです。true または false")

    var z int
    flag.IntVar(&z, "z", 100, "整数を指定します。")

    var g string
    flag.StringVar(&g, "g", "empty", "文字列を入力します。")
    flag.StringVar(&g, "gopher", "empty", "文字列を入力します。")

    flag.Parse()

    fmt.Printf("x: %v, y: %v, z: %v\n", *x, *y, z)
    fmt.Printf("args: %v\n", flag.Args())

    //
    var args = make([]string, 0)

    flag.Visit(func(f *flag.Flag) {
        args = append(args, fmt.Sprintf("%s: %v", f.Name, f.Value))
    })

    fmt.Println("visit: [" + strings.Join(args, ", ") + "]")

    //
    args = make([]string, 0)

    flag.VisitAll(func(f *flag.Flag) {
        args = append(args, fmt.Sprintf("%s: %v", f.Name, f.Value))
    })

    fmt.Println("visitall: [" + strings.Join(args, ", ") + "]")
}

flagを使用する手順は以下のとおりです。

  1. flag.型名の関数かflag.型名Varの関数でオプションの定義を行います。これらの関数は、前者がオプション値へ参照を戻り値とするのに対して、後者は引数でオプション値への参照を渡すだけ違いです。オプション名、デフォルト値、使用方法を設定します。
  2. flag.Parse関数で解析を開始します。
  3. 解析完了後、オプション値への参照先に値が設定されています。
$ go run sample01.go -x=true --z=2 -gopher=hoge Google Apple Microsoft
x: true, y: false, z: 2
args: [Google Apple Microsoft]
visit: [gopher: hoge, x: true, z: 2]
visitall: [g: hoge, gopher: hoge, x: true, y: false, z: 2]

このようにオプションを指定して実行すると、オプションが指定されているものについては指定された値、オプションが指定されていないものについてはデフォルト値が設定されます。
ハイフンの数(ex:-xまたは--x)についての議論がありましたが、

One or two minus signs may be used; they are equivalent.

とドキュメントに書いてあるようにハイフンの数は1つでも2つでも同じものとして扱われるようです。


また、オプションは時々通常のオプション名と省略名を持つ場合があります。この場合の取り扱いも非常に簡単で、

var g string
flag.StringVar(&g, "g", "empty", "文字列を入力します。")
flag.StringVar(&g, "gopher", "empty", "文字列を入力します。")

このようにflag.型名Var関数で同じ参照を設定するだけで解決できます。

$ go run sample01.go -x=true --z=2 -g=hoge Google Apple Microsoft
x: true, y: false, z: 2
args: [Google Apple Microsoft]
visit: [g: hoge, x: true, z: 2]
visitall: [g: hoge, gopher: hoge, x: true, y: false, z: 2]

先の実行結果では-gopherオプションを使用していましたが、-gのオプションでも同じように値を取得することができます。


また、flag.Args関数を実行するとオプション以外に設定されている実行時引数を取得することができます。
こちらに関してもオプション引数とそうでない実行時引数の順番がバラバラだった場合どうなるのかという議論がありましたが、こちらもドキュメントに書いてありました。

Flag parsing stops just before the first non-flag argument ("-" is a non-flag argument) or after the terminator "--".

フラグではない引数が現れた時点で解析が終わるそうです。したがってハイフン付きのオプション引数は必ず先に書かなくてはならないということです。
さらに議論は深まって、

$ go test パッケージ名 -v

はどうなっているのかということになりました。長くなるのでこちらについては別エントリーにまとめられればと思います。(いつになるか分かりませんが)


Visit関数は、オプションで指定されたもののみを列挙します。オプションが指定されずデフォルト値が使用されているものは列挙されません。
それに対して、VisitAll関数は、オプションが指定されずデフォルト値が使用されているものも含めてすべてのオプション値を列挙します。


無効オプションが指定された場合はどうなるのかというと、

$ go run sample01.go -invalid
flag provided but not defined: -invalid
Usage of C:\Users\XXXXXXXX\AppData\Local\Temp\go-build466914003\command-line-arguments\_obj\exe\samp
le01.exe:
  -g="empty": 文字列を入力します。
  -gopher="empty": 文字列を入力します。
  -x=false: xフラグです。true または false
  -y=false: yフラグです。true または false
  -z=100: 整数を指定します。
exit status 2

このようにusageが表示され終了します。uasgeの内容はflag.型名の関数かflag.型名Varの関数で設定した内容が表示されます。またこのサンプルでは-xはbool型を想定していますが、-x=aaaのように変換できない値を指定された場合も同様にusageを表示して終了します。
「go test -z」とした場合が議論されましたが同じことではないのかと思います。

サンプル2

だいたいの内容はすでにまとめたのですがもう少しだけ。
独自の型でオプション値を受け取りたいときは、独自の型でflag.Valueinterfaceを実装します。
その後の使用方法等は通常と同じなので、ソースコードと実行結果のみ掲載しておきます。

package main

import (
    "errors"
    "flag"
    "fmt"
)

type myType string

func (m *myType) String() string {
    return string(*m)
}

func (m *myType) Set(value string) error {
    if string(*m) != "" {
        return errors.New("すでに値が設定されています。")
    }

    *m = myType("###_" + value + "_###")

    return nil
}

func main() {
    var m myType
    flag.Var(&m, "mytype", "myTypeのオプションです。")

    flag.Parse()

    fmt.Printf("myType: %v\n", m)
}
$ go run sample02.go -mytype=hoge
myType: ###_hoge_###

サンプル3

サンプル1の最後で無効なオプションが指定されて場合usageを表示して終了すると書きましたが、その場合の処理を独自に定義することができます。

package main

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

func main() {
    x := flag.Bool("x", false, "xフラグです。true または false")

    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "パラメータ解析中にエラーが発生しました。\n")
        flag.PrintDefaults()
        os.Exit(2)
    }

    flag.Parse()

    fmt.Printf("x: %v\n", *x)
}

このようにflag.Usage変数に処理を定義した関数を設定することで独自の処理を行うことができます。

$ go run sample03.go -invalid
flag provided but not defined: -invalid
パラメータ解析中にエラーが発生しました。
  -x=false: xフラグです。true または false
exit status 2

flag.PrintDefaults関数は定義したすべてのオプションのデフォルト値、使用方法をSdterrに対して出力します。

その他

FlagSetはflagとUsageを定義した1つのまとまりのようなものです。詳細な説明は省略します。

次回

次回はnetパッケージを見ていくそうです。