开发一个 命令行工具,视复杂程度,一般要选择一个合适的命令行解析库,简单的需求用 Go 标准库 flag 就够了,flag 的使用非常简单。
当然,除了标准库 flag 外,也有不少的第三方库。比如,为了替代 flag 而生的 pflag[1],它支持 POSIX 风格的命令行解析。关于 POSIX 风格,本文末尾有个简单的介绍。
更多与命令行处理相关的库,可以打开 awesome-go#command-line[2] 命令行一节查看,star 最多的是 spf13/cobra[3] 和 urfave/cli[4] ,与 flag / pflag 相比,它们更加复杂,是一个完全的全功能的框架。
有兴趣都可以了解下。
目标案例
回归主题,继续介绍 flag 吧。通过案例介绍包的使用会比较直观。
举一个例子说明吧。假设,现在要开发一个 Go 语言环境的版本管理工具,gvg(go version management by go)。
命令行的帮助信息如下:
NAME:
gvg - go version management by go
USAGE:
gvg [global options] command [command options] [arguments...]
VERSION:
0.0.1
COMMANDS:
list list go versions
install install a go version
info show go version info
use select a version
uninstall uninstall a go version
get get the latest code
uninstall uninstall a go version
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help
--version, -v print the version
这个命令不仅包含了全局的选项,还有 8 个子命令,部分子命令支持参数和选项。暂时,子命令的选项参数先不列出来了,实现时再看。
接下来,我们试着通过 flag 实现这个效果。本文只介绍 GLOBAL OPTIONS(全局选项)的实现。
如果想了解什么是 Go 语言环境的版本管理,可以查看 如何灵活地进行 Go 版本管理 一文。
选项表示
最简单的命令不需要任何参数和选项,复杂一点,要支持参数和选项的配置。gvg 没有全局参数,或者说全局参数是子命令,全局选项有 --help -h 和 --version -h。
一个选项在 flag 包中用一个 Flag 表示,那 -h 可以用一个 Flag 表示。一个选项通常由几个部分组成,如名称、使用说明和默认值。如果将 -h 用代码表示,如下:
h := flag.Bool("h", false, "show help")
定义了一个布尔类型的 Flag,名为 h,默认值是 false,使用说明为 "show help"。变量 h 是一个布尔型的指针,通过它可以取出命令行传入的值。
除了使用 flag.Bool,还可以使用另外一种方式定义一个 Flag。我们可以用这种方式定义 -v 选项。
代码如下:
var v bool
flag.BoolVar(&v, "v", false, "print the version")
最后的三个参数含义与 flag.Bool 相同,主要区别在值的获取方式,flag.BoolVar 是通过将变量地址传入获取值。从经验来看,第二种方式使用的较多,或许因为第一种方式会发生变量逃逸。
更多类型
除了布尔类型,Flag 的类型还有整数(int、int64、uint、uint64)、浮点数(float64)、字符串(string)和时长(time.Duration)。
假设 gvg 的案例中,支持配置文件选项 --config-path。实现代码如下:
var configPath
flag.StringVar(&configPath, "config-path", "", "config file path")
通过 StringVar 定义了新的 Flag。使用方式与 BoolVar 相同,最后的三个参数分别是选项名称、默认值和使用说明。
虽然 flag 支持的内置类型并不多,但已经满足大部分需求了。如果有自定义的需求,也可以扩展新的类型实现,这部分内容下篇介绍。
长短选项
现在已经完成了 -h 和 -v 两个选项,但目标是 -v --version 和 -h --help,即同时支持长短选项。
一个 Flag 应该有长短两种形式,但 flag 包并不支持这种风格,需要曲线救国才能实现。(注:本文开头提到的 pflag 支持。)
这里以 -v --version 为例,代码如下:
flag.BoolVar(&v, "v", false, "print the version")
flag.BoolVar(&v, "version", false, "print the version")
定义了两个 Flag,同时绑定到了一个变量上。这种效果只能用 flag.BoolVar 方式定义新的 Flag,flag.Bool 没办法做到将同一个变量同时绑定两个 Flag。
但其实这种也有缺点,先不说了,后面介绍帮助信息打印时就明白了。