命令行参数解析工具 —— Go Pflag 入门

707 阅读5分钟

本文通过丰富的代码示例演示了 pflag 的基本功能,读者可以跟随这些示例,学习如何使用 pflag

Pflag

pflag 是一个用来替代 go 语言标准库中的 flag 包的第三方包,它实现了 POSIX/GNU 风格的命令行参数解析,支持更多的参数类型和功能,同时也兼容 flag 包的用法,因此我们在使用 pflag 包时,可以将其别名为 flag:

import flag "github.com/spf13/pflag"

为程序设置 flag

指针式

我们可以声明一个 flag 并将其存储在指针中:

func main() {
	var port = flag.Int("port", 8080, "specify the listening port number")

	flag.Parse()

	fmt.Printf("listening to the port: %d\n", *port)
}

编译运行程序:

go build main.go
./main
#listening to the port: 8080

查看帮助信息:

./main -h
#Usage of D:\workspace\golang\src\pflag-demo\temp\main.exe:
#      --port int   specify the listening port number (default 8080)
#pflag: help requested

传入指定参数:

./main --port=4399
#listening to the port: 4399

变量式

我们可以使用 Var() 函数将 flag 绑定到变量:

func main() {
	var port int
	flag.IntVar(&port, "port", 8080, "specify the listening port number")

	flag.Parse()

	fmt.Printf("listening to the port: %d\n", port)
}

运行程序:

go run main.go --port=4399
#listening to the port: 4399

自定义标志类型

我们可以创建满足 Value 接口的自定义标志,对于此类标志,默认值只是变量的初始值。

// Port is custom flag type that satisfies the Value interface.
type Port struct {
	value string
}

// String returns the current value of the custom flag.
func (c *Port) String() string {
	return c.value
}

// Set sets the value of the custom flag.
func (c *Port) Set(val string) error {
	c.value = val
	return nil
}

// Type returns the type of the custom flag.
func (c *Port) Type() string {
	return "port"
}

func main() {
	var port Port
	flag.Var(&port, "port", "specify the listening port number")

	flag.Parse()

	fmt.Printf("listening to the port: %s\n", port)
}

运行程序:

go run main.go
#listening to the port: {}

go run main.go --port=4399
#listening to the port: {4399}

flag set

我们可以创建多组独立的 flag:

func main() {
	FS1 := flag.NewFlagSet("FS1", flag.ExitOnError)
	port := FS1.String("port", "8080", "specify the listening port number")

	FS2 := flag.NewFlagSet("FS2", flag.ExitOnError)
	addr := FS2.String("addr", "127.0.0.1", "specify the http address")

	if len(os.Args) < 2 {
		fmt.Println("must use flag set.")
		os.Exit(1)
	}

	switch os.Args[1] {
	case "FS1":
		_ = FS1.Parse(os.Args[2:])
		fmt.Printf("listening to port: %s\n", *port)

	case "FS2":
		_ = FS2.Parse(os.Args[2:])
		fmt.Printf("listening to addr: %s\n", *addr)

	default:
		fmt.Println("no such flag set exists.")
	}
}

运行程序:

go run main.go
#must use flag set.
#exit status 1

go run main.go FS1 --port=4399
#listening to port: 4399

go run main.go FS2 --addr=localhost
#listening to addr: localhost

辅助函数

我们可以使用辅助函数来获取 flag set 中 flag 的值:

func main() {
	fs := flag.NewFlagSet("fs", flag.ExitOnError)

	var port int
	fs.IntVar(&port, "port", 8080, "specify the listening port number")

	_ = fs.Parse(os.Args[1:])

	n, _ := fs.GetInt("port")
	fmt.Println("listening to port:", n)

	fmt.Println("listening to port:", port)
}

运行程序:

go run main.go --port=4399
#listening to port: 4399
#listening to port: 4399

额外参数

我们可以使用 flag.Args() 来获取非标志参数,也可以使用 flag.Arg(i) 来逐个获取参数。

func main() {
	port := flag.String("port", "8080", "specify the listening port number")

	flag.Parse()

	fmt.Printf("listening to port: %s\n", *port)

	for i := 0; i < len(flag.Args()); i++ {
		fmt.Printf("additional arg %d : %s\n", i, flag.Arg(i))
	}
}

运行程序:

go run main.go 4399 7k7k 2144
#listening to port: 8080
#additional arg 0 : 4399
#additional arg 1 : 7k7k
#additional arg 2 : 2144

简写标志

我们可以为 flag 提供简写:

func main() {
	port := flag.IntP("port", "p", 8080, "specify the listening port number")

	flag.Parse()

	fmt.Printf("listening to port: %d\n", *port)
}

运行程序:

go run main.go -p=4399
#listening to port: 4399

无选项默认值

我们可以为 flag 设置无选项默认值(NoOptDefVal),无选项默认值指的是使用 flag 但没有提供选项(参数)时,flag 被赋予的默认值。

func main() {
	var port int
	flag.IntVarP(&port, "port", "p", 8080, "specify the listening port number")
	flag.Lookup("port").NoOptDefVal = "4399"

	flag.Parse()

	fmt.Printf("listening to port: %d\n", port)
}

运行程序:

go run main.go
#listening to port: 8080

go run main.go -p
#listening to port: 4399

go run main.go -p=2144
#listening to port: 2144

命名标准化

命名转换

我们可以自定义标准化函数,将输入的 flag 名 转换为标准的 flag 名:

func wordSepNormalizeFunc(f *flag.FlagSet, name string) flag.NormalizedName {
	from := []string{"-", "_"}
	to := "."
	for _, sep := range from {
		name = strings.Replace(name, sep, to, -1)
	}

	return flag.NormalizedName(name)
}

func main() {
	fs := flag.NewFlagSet("fs", flag.ExitOnError)
	fs.SetNormalizeFunc(wordSepNormalizeFunc)

	fs.String("http.port", "8080", "specify the listening port number")
	_ = fs.Parse(os.Args[1:])

	port := fs.Lookup("http.port").Value.String()
	fmt.Println("listening to port:", port)
}

运行程序:

go run main.go --http-port=4399
#listening to port: 4399

go run main.go --http_port=4399
#listening to port: 4399

go run main.go --http.port=4399
#listening to port: 4399

别名

我们可以自定义别名函数,为 flag 添加别名:

func aliasNormalizeFunc(f *flag.FlagSet, name string) flag.NormalizedName {
	switch name {
	case "portNum":
		name = "port"
		break
	}

	return flag.NormalizedName(name)
}

func main() {
	fs := flag.NewFlagSet("fs", flag.ExitOnError)
	fs.SetNormalizeFunc(aliasNormalizeFunc)

	fs.String("port", "8080", "specify the listening port number")

	_ = fs.Parse(os.Args[1:])

	port := fs.Lookup("port").Value.String()
	fmt.Println("listening to port:", port)
}

运行程序:

go run main.go --portNum=4399
#listening to port: 4399

弃用

我们可以弃用 flag 或 flag 的简写,弃用的 flag 或简写不会出现在帮助信息中,当我们使用弃用的 flag 或简写,控制台会打印弃用信息。

标志

func main() {
	fs := flag.NewFlagSet("fs", flag.ExitOnError)

	var port int
	fs.IntVar(&port, "port", 8080, "specify the listening port number")
	_ = fs.MarkDeprecated("port", "please use --portNum instead.")

	_ = fs.Parse(os.Args[1:])
}

编译程序,然后查看帮助信息:

go build main.go
./main -h
#Usage of fs:
#pflag: help requested

运行程序:

./main -p=4399
#Flag shorthand -p has been deprecated, please use --port only

简写

func main() {
	fs := flag.NewFlagSet("fs", flag.ExitOnError)

	var port int
	fs.IntVarP(&port, "port", "p", 8080, "specify the listening port number")
	_ = fs.MarkShorthandDeprecated("port", "please use --port only")

	_ = fs.Parse(os.Args[1:])
}

编译程序,然后查看帮助信息:

go build main.go
./main -h
#Usage of fs:
#      --port int   specify the listening port number (default 8080)
#pflag: help requested

运行程序:

./main -p=4399
#Flag shorthand -p has been deprecated, please use --port only

隐藏 flag

我们可以将 flag 设置为隐藏,隐藏的 flag 仍可以使用,但是不会显示在帮助信息中。

func main() {
	fs := flag.NewFlagSet("fs", flag.ExitOnError)

	var secretFlagValue string
	fs.StringVar(&secretFlagValue, "secretFlag", "", "A secret flag for internal use")
	_ = fs.MarkHidden("secretFlag")

	_ = fs.Parse(os.Args[1:])

	fmt.Println("secretFlag value:", secretFlagValue)
}

编译程序,然后查看帮助信息:

go build main.go
./main -h
#Usage of fs:
#pflag: help requested

使用隐藏参数:

./main --port=4399
#listening to port: 4399

禁用排序

默认情况下,我们定义的 flag 在帮助信息中会首字母的顺序排序,我们可以手动关闭。

func main() {
	fs := flag.NewFlagSet("fs", flag.ExitOnError)

	fs.String("C", "", "it's flag C.")
	fs.String("B", "", "it's flag B.")
	fs.String("A", "", "it's flag A.")

	fmt.Println("Flags:")
	fs.PrintDefaults()

	fmt.Println("===============================")

	fs.SortFlags = false

	fmt.Println("Flags:")
	fs.PrintDefaults()
}

运行程序:

go run main.go
#Flags:
#      --A string   it's flag A.
#      --B string   it's flag B.
#      --C string   it's flag C.
#===============================
#Flags:
#      --C string   it's flag C.
#      --B string   it's flag B.
#      --A string   it's flag A.

本文由 Sue211213 原创,发表于 2023 年 8 月 10 日,转载请注明出处和作者,谢谢!