go言語でのコマンドライン引数の使い方でやや混乱したのでまとめる。
- まとめること
- flagの仕様
- flagで
git push -f
のようにオプション引数より前に非オプションの引数を置く方法・サブコマンドの例 - go-flagパッケージのサブコマンドを試す
flagの仕様
Golang標準のflag
パッケージは以下のように使う。
package main import ( "flag" "fmt" "os" ) func main() { // -hオプション用文言 flag.Usage = func() { fmt.Fprintf(os.Stderr, ` Usage of %s: %s [OPTIONS] ARGS... Options\n`, os.Args[0],os.Args[0]) flag.PrintDefaults() } var ( opt1 = flag.String("opt1", "default-value", "First string option") opt2 = flag.String("opt2", "default-value", "Second string option") ) flag.Parse() fmt.Println("opt1:", *opt1) fmt.Println("opt2:", *opt2) fmt.Println("args:", flag.Args()) }
$ ./argtest -h Usage of ./argtest: ./argtest [OPTIONS] ARGS... Options -opt1="default-value": First string option -opt2="default-value": Second string option $ ./argtest -opt1 aaa -opt2 bbb AAA BBB opt1: aaa opt2: bbb args: [AAA BBB]
flag.Parse()
関数は、"-"で始まらない(非フラグな)コマンドライン引数に到達すると、未解釈の引数をflag.Args()
に格納し、パースをストップしてしまう。
なので、例:./argtest -opt1 aaa -opt2 bbb FILE1 FILE2...
のようにオプション群を非オプション引数より前に置くのなら簡単にパースできる
flagでコマンドラインオプションをコマンドライン引数の後ろに置きたい
では、例:./argtest FILE1 -opt1 aaa -opt2 bbb
という風にオプションを引数の後ろには書けないのだろうか。たとえばgit push -f
みたいなサブコマンドをやりたいときに必要になる。
そういうときは、FlagSet型
を使う。
これは複数のFlag設定を切り替えられるようにする仕組みだけど、FlagSetのParse()メソッドはパースする範囲をスライスとして渡せるので、これを利用して先頭に非フラグの引数を置いたり、サブコマンドを実現したりできる。
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError) var ( opt1 = fs.String("opt1", "default-value", "First string option") opt2 = fs.String("opt2", "default-value", "Second string option") ) arg1 := os.Args[1] fs.Parse(os.Args[2:]) fmt.Println("arg1:", arg1) fmt.Println("opt1:", *opt1) fmt.Println("opt2:", *opt2) fmt.Println("args:", fs.Args())
$ ./argtest XXX -opt1 AAA -opt2 BBB YYY ZZZ arg1: XXX opt1: AAA opt2: BBB args: [YYY ZZZ]
サブコマンドの実装についてはVegetaが参考になった。各サブコマンド内で別々のフラグを設定している。See vegeta.
- https://github.com/tsenart/vegeta/blob/master/main.go#L39
- https://github.com/tsenart/vegeta/blob/master/attack.go#L15-L25
- https://github.com/tsenart/vegeta/blob/master/report.go#L11-L15
一応、こんなかんじになる。
go-flags
を試してみる
調査の一環で非標準パッケージのgo-flags
を試してみたので感想
- フラグ・非フラグの順序関係なくすべてパースできる。便利だけど、逆に位置を固定したいときにどうすればいいのか分からなかった。
- サブコマンドをサポートしているようなので試してみた(下記)けど、コマンドが
go-flags
に依存してしまうしちょっと大げさだった - 使わなくていいかも。
package main import ( "fmt" "github.com/jessevdk/go-flags" "log" "os" ) type Options struct { Option1 string `short:"1" long:"opt1" description:"First string option"` Option2 string `short:"2" long:"opt2" description:"Second string option"` Put PutCommand `description:"Command to put something" command:"put" subcommands-optional:"true"` Get GetCommand `description:"Command to put something" command:"get" subcommands-optional:"true"` } var opts Options var parser = flags.NewParser(&opts, flags.Default) type PutCommand struct { PutOption string `short:"p" long:"put-opt" description:"First string option"` } type GetCommand struct { GetOption string `short:"g" long:"get-opt" description:"First string option"` } func (x *PutCommand) Execute(args []string) error { fmt.Printf("Putting someting (opt=%v): %#v\n", x.PutOption, args) return nil } func (x *GetCommand) Execute(args []string) error { fmt.Printf("Getting someting (opt=%v): %#v\n", x.GetOption, args) return nil } func main() { if _, err := parser.Parse(); err != nil { os.Exit(1) } }
$ ./command --opt1=AAA put XXX YYY ZZZ --put-opt=OPT Putting someting (opt=OPT): []string{"XXX", "YYY", "ZZZ"} $ ./command --opt1=AAA get XXX YYY ZZZ --get-opt=OPT Getting someting (opt=OPT): []string{"XXX", "YYY", "ZZZ"} $ ./command --opt1=AAA Please specify one command of: get or put ./command VVV --opt1=AAA get XXX YYY ZZZ --get-opt=OPT Getting someting (opt=OPT): []string{"VVV", "XXX", "YYY", "ZZZ"} // <- argsにgetより前の引数が含まれている