title: [Reprint] Parsing Command Line Arguments in Go Standard Library: Detailed Explanation of the flag Package
date: 2021-09-07 13:12:00
comment: false
toc: true
category:
- Golang
tags: - Reprint
- Golang
- Command Line
- Arguments
- Parsing
- flag
- Detailed Explanation
- Standard Library
This article is a reprint from: Parsing Command Line Arguments in Go Standard Library: Detailed Explanation of the flag Package
There are many ways to handle command line arguments in a Go program.
In simple cases, you can use os.Args
directly without any libraries.
package main
import (
"fmt"
"os"
)
func main() {
// os.Args is a []string
if len(os.Args) > 0 {
for index, arg := range os.Args {
fmt.Printf("args[%d]=%v\n", index, arg)
}
}
}
Try running it, the first argument is the path of the executable file.
$ go run demo.go hello world hello golang
args[0]=/var/folders/72/lkr7ltfd27lcf36d75jdyjr40000gp/T/go-build187785213/b001/exe/demo
args[1]=hello
args[2]=world
args[3]=hello
args[4]=golang
As you can see from the above, os.Args
can only handle simple arguments and has strict requirements on the position of the arguments. For more complex scenarios, you need to define your own parsing rules, which can be very cumbersome.
If you really encounter a so-called complex scenario, you can use the flag package in the Go standard library to handle command line arguments.
This article will introduce the usage of the flag package in the Go standard library.
Types of Arguments#
Based on whether the argument is a boolean type, it can be divided into two types:
- Boolean arguments: such as
--debug
, no specific value needs to be provided afterwards, specifying it means True, not specifying it means False. - Non-boolean arguments: non-boolean arguments, which may be of other types such as int, string, etc., such as
--name jack
, which can be followed by specific parameter values.
Based on the length of the argument name, it can also be divided into:
- Long arguments: for example,
--name jack
is a long argument, with two-
before the argument name. - Short arguments: usually one or two letters (abbreviations of the corresponding long arguments), such as
-n
, with only one-
before the argument name.
Getting Started Example#
Let's start with an example of a string type argument.
package main
import (
"flag"
"fmt"
)
func main(){
var name string
flag.StringVar(&name, "name", "jack", "your name")
flag.Parse() // Parse the arguments
fmt.Println(name)
}
flag.StringVar
defines a string argument, which takes several parameters:
- The first parameter: where to store the value after receiving it, it needs to be a pointer.
- The second parameter: the parameter name used in the command line, such as
--name jack
, the name is name. - The third parameter: if the parameter value is not specified in the command line, the default value is
jack
. - The fourth parameter: records the purpose or meaning of this parameter.
After running the above program, the output is as follows:
$ go run demo.go
jack
$ go run demo.go --name wangbm
wangbm
Improving the Code#
If your program only accepts a few parameters, there is no problem with writing it like the above example.
However, once the number of parameters increases, a lot of code for parameter parsing will accumulate in the main function, affecting the readability and aesthetics of the code.
It is recommended to put the code for parameter parsing in the init
function, which is executed before the main
function.
package main
import (
"flag"
"fmt"
)
var name string
func init() {
flag.StringVar(&name, "name", "jack", "your name")
}
func main(){
flag.Parse()
fmt.Println(name)
}
Parameter Types#
When you specify a parameter in the command line, how does Go parse this parameter and convert it to a specific type? This needs to be defined in advance by you.
Different parameters correspond to different methods in the flag
package.
Let's talk about different parameter types and how to define them.
Boolean#
Implementation: When --debug
is not specified, the default value of debug is false. If you specify --debug
, debug is assigned true.
var debug bool
func init() {
flag.BoolVar(&debug, "debug", false, "whether to enable DEBUG mode")
}
func main(){
flag.Parse()
fmt.Println(debug)
}
After running, the execution results are as follows:
$ go run main.go
false
$ go run main.go --debug
true
Numeric#
Define an age
parameter, default to 18 if not specified.
var age int
func init() {
flag.IntVar(&age, "age", 18, "your age")
}
func main(){
flag.Parse()
fmt.Println(age)
}
After running, the execution results are as follows:
$ go run main.go
18
$ go run main.go --age 20
20
int64
, uint
, and float64
types correspond to Int64Var
, UintVar
, and Float64Var
methods respectively, which are the same, so I won't repeat them.
String#
Define a name
parameter, default to jack if not specified.
var name string
func init() {
flag.StringVar(&name, "name", "jack", "your name")
}
func main(){
flag.Parse()
fmt.Println(name)
}
After running, the execution results are as follows:
$ go run main.go
jack
$ go run main.go --name wangbm
wangbm
Time#
Define an interval
parameter, default to 1s if not specified.
var interval time.Duration
func init() {
flag.DurationVar(&interval, "interval", 1 * time.Second, "loop interval")
}
func main(){
flag.Parse()
fmt.Println(interval)
}
Verify the effect as follows:
$ go run main.go
1s
$ go run main.go --interval 2s
2s
Custom Types#
The flag package supports types such as Bool, Duration, Float64, Int, Int64, String, Uint, and Uint64.
These types of parameters are encapsulated into their corresponding backend types, such as Int type parameters are encapsulated into intValue, String type parameters are encapsulated into stringValue.
These backend types all implement the flag.Value interface, so a command line parameter can be abstracted as an instance of the Flag type. The following is the code for the Value interface and the Flag type:
type Value interface {
String() string
Set(string) error
}
// Flag type
type Flag struct {
Name string // name as it appears on command line
Usage string // help message
Value Value // value as set, it is an interface, so it can be an instance of different types.
DefValue string // default value (as text); for usage message
}
func Var(value Value, name string, usage string) {
CommandLine.Var(value, name, usage)
}
To implement a custom type of parameter, you only need the first parameter object of the Var function to implement the flag.Value interface.
type sliceValue []string
func newSliceValue(vals []string, p *[]string) *sliceValue {
*p = vals
return (*sliceValue)(p)
}
func (s *sliceValue) Set(val string) error {
// How to parse the parameter value
*s = sliceValue(strings.Split(val, ","))
return nil
}
func (s *sliceValue) String() string {
return strings.Join([]string(*s), ",")
}
For example, I want to achieve the following effect, the passed-in parameter is a string separated by commas, and the flag will convert it to a slice during parsing.
$ go run demo.go -members "Jack,Tom"
[Jack Tom]
Then I can write the code like this:
var members []string
type sliceValue []string
func newSliceValue(vals []string, p *[]string) *sliceValue {
*p = vals
return (*sliceValue)(p)
}
func (s *sliceValue) Set(val string) error {
// How to parse the parameter value
*s = sliceValue(strings.Split(val, ","))
return nil
}
func (s *sliceValue) String() string {
return strings.Join([]string(*s), ",")
}
func init() {
flag.Var(newSliceValue([]string{}, &members), "members", "member list")
}
func main(){
flag.Parse()
fmt.Println(members)
}
Some friends may have questions about the line of code (*sliceValue)(p)
, what does it mean?
Regarding this, in fact, it was mentioned in 2.9 Detailed Explanation: Static Types and Dynamic Types before. If you forget, you can review it.
Long and Short Options#
In fact, the flag package does not distinguish between long and short options in usage. You can see the following example.
package main
import (
"flag"
"fmt"
)
var name string
func init() {
flag.StringVar(&name, "name", "Ming", "your name")
}
func main(){
flag.Parse()
fmt.Println(name)
}
By specifying the following parameter forms:
$ go run main.go
Ming
$ go run main.go --name jack
jack
$ go run main.go -name jack
jack
One -
and two -
have the same result.
What about adding one more?
Finally, an error occurred. It indicates that at most two -
can be specified.
$ go run main.go ---name jack
bad flag syntax: ---name
Usage of /tmp/go-build245956022/b001/exe/main:
-name string
your name (default "Ming")
exit status 2