前言

命令行参数虽然在编程语言看似不起眼,但其实也发挥着很大的作用,比如对于一个HTTP服务器来说,通过传递命令行参数,可以非常便捷地控制服务器的端口,或者其他参数。

而在Golang中,实现命令行参数的方式也非常多,有Go自带的flag库,也有第三方的 spf13/pflag、jessevdk/go-flags、urfave/cli、spf13/cobra。

gogit

快速上手

对于想要快速上手的朋友,个人还是建议先熟悉Go自带的flag库,因为其他库基本都是这个库的扩展和延伸。

Go自带的flag库的使用非常简单,一般都是三步走:第一步是定义命令行参数,第二步是解析传入的命令行参数,第三步是根据传入的参数来实现不同的功能,比如下面的程序。

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
)

// 实现一个可定制端口的HTTP服务器

// 第一步是定义命令行参数
var (
	help    bool
	version bool
	cfgPath string
	cfgPort int
)

func init() {
	flag.BoolVar(&help, "help", false, "the usage of http server")
	flag.BoolVar(&version, "version", false, "the version of http server")
	flag.StringVar(&cfgPath, "cfgPath", "conf.yaml", "the config path of http server")
	flag.IntVar(&cfgPort, "cfgPort", 8001, "the port of http server")
}

func main() {
	// 第二步是解析传入的命令行参数
	flag.Parse()

  // 第三步就是根据传入的参数来实现不同的功能
	if help {
		flag.Usage()
		return
	}

	if version {
		_, err := fmt.Fprint(os.Stdout, "http server version: v1.0.0")
		if err != nil {
			log.Fatal(err)
		}
		return
	}

	// 启动服务器
	fmt.Printf("server start, port is: %v", cfgPort)
}
复制代码

执行效果如下(我这里为了简单,直接 run 而不是 build):

$ go run flag_v0.go -help
Usage of /var/folders/08/_11f1hlj68j_1bkxxrn4q1gh0000gn/T/go-build191621593/b001/exe/flag_v0:
  -cfgPath string
        the config path of http server (default "conf.yaml")
  -cfgPort int
        the port of http server (default 8001)
  -help
        the usage of http server
  -version
        the version of http server

$ go run flag_v0.go -version
http server version: v1.0.0%                                                                                                                              

$ go run flag_v0.go -cfgPort 8003
server start, port is: 8003%                            
复制代码

使用 flag 的注意事项

在使用 flag 库的时候,我也遇到几个小坑,这里提醒下:

flag.Parse()
$ go run flag_v0.go -version  # version 的结果为 true
$ go run flag_v0.go -version 1 # version 的结果为 true
$ go run flag_v0.go -version true # version 的结果为 true
$ go run flag_v0.go -version=false # version 的结果为 false
复制代码
flag.Args()non-flag
$ go run flag_v0.go -version true # flag.Args() 为 []
复制代码

程序优化

上面的程序虽然能基本满足我们的要求,但还存在一些小问题,第一个是 usage 提示的信息不够优雅,第二个是不支持短选项

flag.Usage
var (
	help    bool
	version bool
	cfgPath string
	cfgPort int
)

func usage() {
	fmt.Fprintf(os.Stderr, `a simple http server create by yangan

Options:
`)
	flag.PrintDefaults()
}

func init() {
	flag.BoolVar(&help, "help", false, "the usage of http server")
  ... // 省略
	// 重写 usage
	flag.Usage = usage
}
复制代码

执行效果如下:

 go run flag_v1.go --help
a simple http server create by yangan  # 换成我们自己的提示语

Options:
  -cfgPath string
        the config path of http server (default "conf.yaml")
...
复制代码
flagpflaggo-flags

1.flag的解决方案

func init() {
	flag.BoolVar(&help, "help", false, "the usage of http server")
	flag.BoolVar(&help, "h", false, "the usage of http server")

	flag.BoolVar(&version, "version", false, "the version of http server")
	flag.BoolVar(&version, "v", false, "the version of http server")

	flag.StringVar(&cfgPath, "cfgPath", "conf.yaml", "the config path of http server")
	flag.StringVar(&cfgPath, "c", "conf.yaml", "the config path of http server")

	flag.IntVar(&cfgPort, "cfgPort", 8001, "the port of http server")
	flag.IntVar(&cfgPort, "p", 8001, "the port of http server")

	// 重写 usage
	flag.Usage = usage
}
...
复制代码

2.pflag的解决方案

// 支持短选项(注意带有P)
func init() {
	flag.BoolVarP(&help, "help", "h", false, "the usage of http server")
	flag.BoolVarP(&version, "version", "v", false, "the version of http server")
	flag.StringVarP(&cfgPath, "cfgPath", "c", "conf.yaml", "the config path of http server")
	flag.IntVarP(&cfgPort, "cfgPort", "p", 8001, "the port of http server")

	// 重写 usage
	flag.Usage = usage
}
复制代码

flag 和 pflag 对比

1.pflag 可以很方便地支持短选项

flag.BoolVarP(&help, "help", "h", false, "the usage of http server")
复制代码

2.pflag 的输出样式比 flag 更好看(纯属个人看法)

# flag的输出
$ go run flag_v1.go --help
a simple http server create by yangan

Options:
  -cfgPath string
        the config path of http server (default "conf.yaml")
  -cfgPort int
        the port of http server (default 8001)
  -help
        the usage of http server
  -version
        the version of http server

# pflag的输出
$ go run pflag_v2.go -h
a simple http server create by yangan

Options:
  -c, --cfgPath string   the config path of http server (default "conf.yaml")
  -p, --cfgPort int      the port of http server (default 8001)
  -h, --help             the usage of http server
  -v, --version          the version of http server
复制代码

3.pflag 可以隐藏、废弃(Deprecated)选项
这个属于比较高级的功能,有时候我们的命令行参数需要隐藏,有时候我们觉得一开始定义的参数有问题,需要废弃,换成新的,使用pflag,可以很方便地实现。

package main

import (
	flag "github.com/spf13/pflag"
	"os"
)
import (
	"fmt"
)

var (
	help       bool
	version    bool
	cfgPath    string
	cfgPort    int
	serverPort int // 使用 cfgPort 代替 serverPort
)

func usage() {
	fmt.Fprintf(os.Stderr, `a simple http server create by yangan

Options:
`)
	flag.PrintDefaults()
}

func init() {
	flag.BoolVarP(&help, "help", "h", false, "the usage of http server")
	flag.BoolVarP(&version, "version", "v", false, "the version of http server")
	flag.StringVarP(&cfgPath, "cfgPath", "c", "conf.yaml", "the config path of http server")
	flag.IntVarP(&cfgPort, "cfgPort", "p", 8001, "the port of http server")
	flag.IntVarP(&serverPort, "serverPort", "s", 8001, "the port of http server")

	// 重写 usage
	flag.Usage = usage
}

func main() {
	// 把 serverPort 参数标记为即将废弃的,请用户使用 cfgPort 参数
	flag.CommandLine.MarkDeprecated("serverPort", "please use cfgPort instead")

	// 把 serverPort 参数的 shorthand 标记为即将废弃的,请用户使用 cfgPort 的 shorthand 参数
	flag.CommandLine.MarkShorthandDeprecated("serverPort", "please use -p instead")

	// 禁止排序
	flag.CommandLine.SortFlags = false

	// 解析参数
	flag.Parse()
	if help {
		flag.Usage()
		return
	}

	fmt.Printf("server start, port is: %v", cfgPort)
}
复制代码

4.pflag 可以禁止选项自动排序 这个属于强迫症的福音,从上面的例子可以看到,我们定义的选项,最终输出时都会进行自动排序,如果想要按照自己的顺序,则可修改 SortFlags 实现

// 完整程序如上
flag.CommandLine.SortFlags = false

复制代码

5.pflag 可以支持选项切片,即传入的参数是切片

import "flag"import flag "github.com/spf13/pflag"

写在最后

如果你觉得本文对你有帮助,麻烦给我一个小爱心,你们的爱心是我创作道路上最大的鼓励! 另外,本文的所有源代码都可以在这里找到:yangancode/go-business

参考