首页 > 解决方案 > 全局标志和子命令

问题描述

我正在实现一个带有多个子命令的小 CLI。我想支持全局标志,即适用于所有子命令以避免重复它们的标志。

例如,在下面的示例中,我尝试-required使用所有子命令所需的标志。

package main

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

var (
    required = flag.String(
        "required",
        "",
        "required for all commands",
    )
    fooCmd = flag.NewFlagSet("foo", flag.ExitOnError)
    barCmd = flag.NewFlagSet("bar", flag.ExitOnError)
)

func main() {
    flag.Parse()

    if *required == "" {
        fmt.Println("-required is required for all commands")
    }

    switch os.Args[1] {
    case "foo":
        fooCmd.Parse(os.Args[2:])
        fmt.Println("foo")
    case "bar":
        barCmd.Parse(os.Args[2:])
        fmt.Println("bar")
    default:
        log.Fatalf("[ERROR] unknown subcommand '%s', see help for more details.", os.Args[1])
    }
}

我希望用法如下:

$ go run main.go foo -required helloworld

但如果我用上面的代码运行它,我会得到:

$ go run main.go foo -required hello
-required is required for all commands
flag provided but not defined: -required
Usage of foo:
exit status 2

看起来flag.Parse()没有-required从 CLI 捕获,然后fooCmd抱怨我给了它一个它无法识别的标志。

在 Golang 中使用带有全局标志的子命令的最简单方法是什么?

标签: gocommand-line-interfacecommand-line-arguments

解决方案


如果您打算实现子命令,则不应调用flag.Parse().

而是决定使用哪个子命令(就像您对 所做的那样os.Args[1]),并且只调用它的FlagSet.Parse()方法。

是的,为此,所有标志集都应包含公共标志。但是很容易注册一次(在一个地方)。创建一个包级变量:

var (
    required string

    fooCmd = flag.NewFlagSet("foo", flag.ExitOnError)
    barCmd = flag.NewFlagSet("bar", flag.ExitOnError)
)

并使用循环遍历所有标志集,并注册公共标志,使用以下命令指向您的变量FlagSet.StringVar()

func setupCommonFlags() {
    for _, fs := range []*flag.FlagSet{fooCmd, barCmd} {
        fs.StringVar(
            &required,
            "required",
            "",
            "required for all commands",
        )
    }
}

main()调用Parse()适当的标志集,然后进行测试required

func main() {
    setupCommonFlags()

    switch os.Args[1] {
    case "foo":
        fooCmd.Parse(os.Args[2:])
        fmt.Println("foo")
    case "bar":
        barCmd.Parse(os.Args[2:])
        fmt.Println("bar")
    default:
        log.Fatalf("[ERROR] unknown subcommand '%s', see help for more details.", os.Args[1])
    }

    if required == "" {
        fmt.Println("-required is required for all commands")
    }
}

您可以通过创建标志集的映射来改进上述解决方案,因此您可以使用该映射来注册公共标志,并进行解析。

完整的应用程序:

var (
    required string

    fooCmd = flag.NewFlagSet("foo", flag.ExitOnError)
    barCmd = flag.NewFlagSet("bar", flag.ExitOnError)
)

var subcommands = map[string]*flag.FlagSet{
    fooCmd.Name(): fooCmd,
    barCmd.Name(): barCmd,
}

func setupCommonFlags() {
    for _, fs := range subcommands {
        fs.StringVar(
            &required,
            "required",
            "",
            "required for all commands",
        )
    }
}

func main() {
    setupCommonFlags()

    cmd := subcommands[os.Args[1]]
    if cmd == nil {
        log.Fatalf("[ERROR] unknown subcommand '%s', see help for more details.", os.Args[1])
    }

    cmd.Parse(os.Args[2:])
    fmt.Println(cmd.Name())

    if required == "" {
        fmt.Println("-required is required for all commands")
    }
}

推荐阅读