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?
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")
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With