Dealing with Command Line Options in Golang: flag package
If you decide to write a command line tool, you’ll most likely want to get user input as command line options and arguments. There are mainly three ways to handle this.
os.Args
functionflag
package- Libraries: go-flags, cli, cobra
I’m going to look at flag
package in this post.
This is not about how to write a command line tool, and I don’t cover handling environmental variables nor subcommands.
Example
Let’s assume we’re supposed to parse the following command in the following sections.
./go-curl -v -X "GET" https://example.com
where -v is a flag to print verbose message, -X is an option to specify the HTTP method, and it takes a url as an argument.
The flag package
flag
is go official utility package that makes it easy to parse command line input. You can take options in bool, int, uint, string, time.Duration, float, and types you define. I show you the entire code first:
func main() {
v := flag.Bool("v", false, "Makes curl verbose during the operation.") var X string
flag.StringVar(&X, "X", "GET", "(HTTP) Specifies a custom request method to use when communicating with the HTTP server.") flag.Parse() args := flag.Args() // do curl here
fmt.Printf("v: %t, X: %s, args: %v\n", *v, X, args) os.Exit(0)
}
There are two ways to define options.
1. Declare the variable as a pointer type
v := flag.Bool("v", false, "Makes curl verbose during the operation.")
The functions Bool, String, Duration, etc. returns a pointer value that contains the option value. You can specify the option name, the default value, and the description. As it’s a pointer, you need to dereference (*v) when accessing it. By using a bool value, you can define the option without a parameter.
2. Use ~Var
function
var X string
flag.StringVar(&X, "X", "GET", "(HTTP) Specifies a custom request method to use when communicating with the HTTP server.")
You can declare the variable first and call the function with Var suffix. Although it becomes multiple lines, you don’t need to care about pointers.
After declaring the options, you need call Parse
function before using them.
flag.Parse()
Once you parse, you can get the arguments by calling flag.Args()
. This is exactly the values after all the options. For example, if the input is ./go-curl -v arg -opt value
, the function returns ["arg", "-opt", "value"]
.
What if user put wrong options?
The process exit with code 2 as shown below:
$ ./go-curl -unknown-option
flag provided but not defined: -unknown-option
Usage of ./go-curl:
-X string
(HTTP) Specifies a custom request method to use when communicating with the HTTP server. (default "GET")
-v Makes curl verbose during the operation.
$ echo $?
2
However, you might want to return your own exit code on exception. Or you might want to continue the process without exiting. This is where FlagSet comes in.
FlagSet
flag package uses FlagSet
struct underneath. When you call flag functions without receiver, it delegates the process to the default FlagSet instance. If you want to change the exceptional behaviour you have three options of how to deal with it.
- ContinueOnError …
Parse
method returns error so you can handle it as you like. - ExitOnError …
Parse
exits with status code 2. - PanicOnError …
Parse
calls panic on exception.
For example, if you want to return 100 as exit status code on exception:
func main() {
fs := flag.NewFlagSet("curl", flag.ContinueOnError)
v := fs.Bool("v", false, "Makes curl verbose during the operation.")
var X string
fs.StringVar(&X, "X", "GET", "(HTTP) Specifies a custom request method to use when communicating with the HTTP server.")
if err := fs.Parse(os.Args[1:]); err != nil {
os.Exit(100)
} ...
}
(Be aware fs.Parse
takes []string as input as opposed to the previous section.)
You can use FlagSet to implement subcommands as well. See the references below for details.
FlagSet prints nice error message to stderr like you saw in the previous section. Occasionally you might want to suppress this message in testing for example. There’s a function SetOutput
to change the output destination.
References
- Official Document: https://golang.org/pkg/flag/
- Subcommand with flag package: https://blog.rapid7.com/2016/08/04/build-a-simple-cli-tool-with-golang/
- spf13/cobra: https://github.com/spf13/cobra
Conclusion
One drawback of the flag package is its syntax as it doesn’t follow the UNIX conventions. Or, you might find it’s not satisfactory in implementing complex subcommands. The libraries such as go-cmdline or cobra could be your help.