はじめに
ここでは私が go 言語を書いているうちに定着したコマインドライン引数の定石を書きます。まず go言語の標準パッケージのコマインドライン引数処理は、python の argparse と比べて機能はかなり限られています。
- long option, short option の区別がない
--flag
,-f
のようにハイフンの数で long,short option を使い分けるという機能はありません。その代わりオプションは何文字でもよく、--flag
も-flag
も同じとみなされます。 - オプション引数は必ず位置引数の前
./cmd -a opt1 -b opt2 arg1 arg2
というコマンドを./cmd arg1 arg2 -a opt1 -b opt2
とするとオプションを認識できなくなります。
python の argparse のようなことをしたかったら外部パッケージを使ってください。
定石
匿名 struct の変数 args
を定義する
以下のようにします。
package main
import (
"fmt"
"os"
"flag"
)
var args struct {
opt1,opt2 string
flag1 bool
}
定石2: flag.*Var
関数でオプション引数をセット
func main() {
flag.StringVar(&args.opt1,"opt1","","option 1")
flag.StringVar(&args.opt2,"opt2","","option 2")
flag.BoolVar(&args.flag1,"flag1",false,"flag 1")
flag.Parse()
}
定石2: 位置引数は flag.Args()
で取得
以下の mainargs[0]
に 1番目の位置引数、mainargs[1]
に 2番目 ... というふうに入ります。ちなみに、os.Args
にはフラグ処理をしないすべての引数が入っています。os.Args
と flag.Args()
を混同しないように!
func main() {
...
flag.Parse()
mainargs := flag.Args()
}
定石3: usage()
関数を用意
メインの説明を書きます。flag の説明は flag.PrintDefaults() を読んでやってもらいます。
func usage() {
fmt.Fprintf(os.Stderr,"usage: %s [flags] [filein]\n",os.Args[0])
flag.PrintDefaults()
}
そして main 関数のほうで flag.Usage = usage
の文を加えてください。
全体像
package main
import (
"fmt"
"os"
"flag"
)
var args struct {
opt1,opt2 string
flag1 bool
}
func usage() {
fmt.Fprintf(os.Stderr,"usage: %s [flags] [filein]\n",os.Args[0])
flag.PrintDefaults()
}
func main() {
flag.Usage = usage
flag.StringVar(&args.opt1,"opt1","","option 1")
flag.StringVar(&args.opt2,"opt2","","option 2")
flag.BoolVar(&args.flag1,"flag1",false,"flag 1")
flag.Parse()
if len(mainargs) < 1 {
usage()
os.Exit(1)
}
MyFunc(mainargs[0],args.opt1)
}
高段者向け
オプション引数をパースして構造体にしたり、複数回の同じオプション引数をリストにしたいとき flag.Var()
が使えます。例えば mail -c cc1 -c cc2 -c cc3
という入力により [cc1,cc2,cc3]
というリストにしたいときです。
まず type
で格納するデータ型を作ります。go 言語 では[]string
のような型でも type
で新しい型として定義でき、メソッドを追加できます。これを使って新しい型を定義して、String() string
と Set(string) error
メソッドを定義します。するとflag.Var()
でセットできるようになります。もちろん、もともと使う予定の既存の型にそれらのメソッドを追加してもよいです。
type cclist []string
func (cc *cclist) String() string {
var s strings.Builder
for _,c := range *cc {
fmt.Fprintf(&s,"%s,",c)
}
return s.String()
}
func (cc *cclist) Set(s string) error {
*c = append(*c,s)
}
このようにして
var args struct (
cc cclist
)
func main() {
...
flag.Var(&args.cc,"cc","cc. for multiple cc use flag repeatedly")
}
そうすると
mail -c cc1 -c cc2 -c cc3
としたら args.cc
: []string{"cc1","cc2","cc3"}
になります。