前言
命令行参数虽然在编程语言看似不起眼,但其实也发挥着很大的作用,比如对于一个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