banner
biuaxia

biuaxia

"万物皆有裂痕,那是光进来的地方。"
github
bilibili
tg_channel

[Reprint] Parsing Command Line Arguments in the Go Language Standard Library: Detailed Explanation of the flag Library

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
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.