英文:
Global flags and subcommands
问题
我正在实现一个带有多个子命令的小型命令行界面(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
标志,然后fooCmd
报错说我给了一个它不认识的标志。
在Golang中,实现具有全局标志的子命令的最简单方法是什么?
英文:
I'm implementing a little CLI with multiple subcommands. I'd like to support global flags, that is flags that apply to all subcommands to avoid repeating them.
For example, in the example below I'm trying to have -required
flag that is required for all subcommands.
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])
}
}
I would expect usage to be like:
$ go run main.go foo -required helloworld
but if I ran that with the above code I get:
$ 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
It looks like flag.Parse()
is not capturing -required
from the CLI, and then the fooCmd
is complaining that I've given it a flag it doesn't recognize.
What's the easiest way to have subcommands with global flags in Golang?
答案1
得分: 15
如果你打算实现子命令,就不应该调用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")
}
}
英文:
If you intend to implement subcommands, you shouldn't call flag.Parse()
.
Instead decide which subcommand to use (as you did with os.Args[1]
), and call only its FlagSet.Parse()
method.
Yes, for this to work, all flag sets should contain the common flags. But it's easy to register them once (in one place). Create a package level variable:
var (
required string
fooCmd = flag.NewFlagSet("foo", flag.ExitOnError)
barCmd = flag.NewFlagSet("bar", flag.ExitOnError)
)
And use a loop to iterate over all flagsets, and register the common flags, pointing to your variable using FlagSet.StringVar()
:
func setupCommonFlags() {
for _, fs := range []*flag.FlagSet{fooCmd, barCmd} {
fs.StringVar(
&required,
"required",
"",
"required for all commands",
)
}
}
And in main()
call Parse()
of the appropriate flag set, and test required
afterwards:
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")
}
}
You can improve the above solution by creating a map of flag sets, so you can use that map to register common flags, and also to do the parsing.
Full app:
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")
}
}
答案2
得分: 3
将全局标志放在子命令之前:
go run . -required=x foo
。
使用flag.Args()
而不是os.Args
:
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")
}
args := flag.Args() // -required标志之后的所有内容,例如[foo, -foo-flag-1, -foo-flag-2, ...]
switch args[0] {
case "foo":
fooCmd.Parse(args[1:])
fmt.Println("foo")
case "bar":
barCmd.Parse(args[1:])
fmt.Println("bar")
default:
log.Fatalf("[ERROR] unknown subcommand '%s', see help for more details.", args[0])
}
}
如果要将所有标志保持在一起,可以编写一个帮助函数,将公共标志添加到每个FlagSet中:
var (
fooCmd = flag.NewFlagSet("foo", flag.ExitOnError)
barCmd = flag.NewFlagSet("bar", flag.ExitOnError)
)
type globalOpts struct {
required string
}
func main() {
var opts globalOpts
addGlobalFlags(fooCmd, &opts)
addGlobalFlags(barCmd, &opts)
if opts.required == "" {
fmt.Println("-required is required for all commands")
}
// ...
}
func addGlobalFlags(fs *flag.FlagSet, opts *globalOpts) {
fs.StringVar(
&opts.required,
"required",
"",
"required for all commands",
)
}
也许你还可以结合这两种方法,使全局标志在任何位置都起作用。
英文:
Put the global flags before the subcommand:
go run . -required=x foo
.
Use flag.Args()
instead of os.Args
:
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")
}
args := flag.Args() // everything after the -required flag, e.g. [foo, -foo-flag-1, -foo-flag-2, ...]
switch args[0] {
case "foo":
fooCmd.Parse(args[1:])
fmt.Println("foo")
case "bar":
barCmd.Parse(args[1:])
fmt.Println("bar")
default:
log.Fatalf("[ERROR] unknown subcommand '%s', see help for more details.", args[0])
}
}
If you want to keep all flags together, after the subcommand, write a helper function that adds common flags to each FlagSet:
var (
fooCmd = flag.NewFlagSet("foo", flag.ExitOnError)
barCmd = flag.NewFlagSet("bar", flag.ExitOnError)
)
type globalOpts struct {
required string
}
func main() {
var opts globalOpts
addGlobalFlags(fooCmd, &opts)
addGlobalFlags(barCmd, &opts)
if opts.required == "" {
fmt.Println("-required is required for all commands")
}
// ...
}
func addGlobalFlags(fs *flag.FlagSet, opts *globalOpts) {
fs.StringVar(
&opts.required,
"required",
"",
"required for all commands",
)
}
Perhaps you can also combine the two approaches to make the global flags work in any position.
答案3
得分: 1
也许你会对使用https://github.com/spf13/cobra感兴趣-它支持这种用例和许多其他用例。
英文:
Maybe you would be interested in using https://github.com/spf13/cobra - it supports exactly this usecase and many others.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论