Go
golang
cobra

cobra / pflags でフラグをパースせずに args に残す

docker run のように,コマンドの引数に別のコマンドを引き渡して内部でいい感じに実行する系のやつ.

FlagSet.SetInterspersed

FlagSet.SetInterspersed を使う.

cmd := &cobra.Command{
    // snip.

    // 何らかのコマンドが渡されてることを保証する(not required だが,大抵は必要なはず)
    Args: cobra.MinimumNArgs(1),
}

// ref: https://godoc.org/github.com/spf13/pflag#FlagSet.SetInterspersed
// `Flags()` or `PersistentFlags()` はユースケースによっていい感じに使い分ける
cmd.Flags().SetInterspersed(false)

cobra(pflags)はデフォルトだと引数とオプションがごちゃごちゃ(interspersed)に渡ってきてもちゃんとパースされる.なのでこれを off にすることで,最初の flag じゃない引数より後ろは flag としてパースされなくなる.

実装は pflags の flag.go にあって,最初の引数が見つかった瞬間に flag のパースをやめるというもの.

日本語だと意味不明だけど,だいたい :point_down: みたいな挙動になる.

$ # intespersed: true (default)
$ # args == []string{"run", "qux", "quux"}
$ fooctl --verbose run --bar=42 qux --baz quux

$ # intespersed: false
$ # args == []string{"run", "--bar=42", "qux", "--baz", "quux"}
$ fooctl --verbose run --bar=42 qux --baz quux

SetInterspersed(false) でも,上の例だと run より後ろ(--verbose)は正しくパースされる.もちろん,未定義のフラグならエラーになる.

UnknownFlags?

cobra.CommandFParseErrWhitelist.UnknownFlags というものがあるが,これは単に未知のフラグを無視するようになるだけなので,今回は関係ない.

余談

docker runrunSetInterspersed(false) をしている.
https://github.com/docker/cli/blob/v18.09.0/cli/command/container/run.go#L52-L53

一方,ほぼ同一の interface を持つ kubectl run は interspersed が true のまま.
https://github.com/kubernetes/kubernetes/blob/v1.12.2/pkg/kubectl/cmd/run.go#L166-L193

pflgas は -- 以後の引数はパースしないので,そうやってコマンドを渡す.
yarn run とかとおなじ,わりと一般的な挙動ではある.

$ # args == []string{"curl", "http://example.com/ping"}
$ kubectl run ... --image buildpack-deps:18.04-curl -- curl http://example.com/ping

interspersed を切るかどうかは,その CLI がどんな UX を提供したいかによって判断すると良さそう.