Golang开发命令行工具之flag包的使用
介绍使用 Go 原生 flag 包开发命令行工具的方法与常见用法。

命令行工具概述
日常命令行操作,相对应的众多命令行工具是提高生产力的必备工具,我在之前的文章我的生产力工具推荐-终端01篇中有推荐过一些我常用的基于terminal终端的命令行cli工具
鼠标能够让用户更容易上手,降低用户学习成本。 而对于开发者,键盘操作模式能显著提升生产力,还有在一些专业工具中, 大量使用快捷键代替繁琐的鼠标操作,能够使开发人员更加专注于工作,提高效率,因为键盘操作模式更容易产生肌肉记忆
举个栗子。我司业务研发,前些年在我们的强力推动下(被迫)转向使用了git作为版本控制,开始使用的是图形化“小乌龟”工具。后续出现几次问题解决起来较麻烦后,推荐其使用原生的git命令行。如今,使用git命令行操作版本控制可谓 “一顿操作猛如虎......”
命令行(键盘)操作在很大程度上可以提高工作效率,与之相对应的是鼠标(触屏等)操作,这两种模式是目前的主流人机交互方式
设计一款命令行工具的开发语言可以选择原始的shell、甚至是更原始的语言C,更为容 易上手且功能更多的有node、python、golang
本文是基于golang开发命令行工具的开篇,主要是基于golang原生内置的、轻量的flag包实现,用golang设计命令行工具而不用shell、python的原因这里就不做论述了
flag包介绍
flag包用来解析命令行参数
相比简单的使用os.Args来获取命令行参数,flag可以实现按照更为通用的命令行用法,例如mysql -u root -p 123456。其中mysql是命令行的名称即这个命令,-u和-p分别是这个命令的两个参数:用户名和密码,后面接着的是对应的参数值,有了参数的声明之后,两个参数可以互换位置,参数值也可以选填或按照缺省(默认)值进行指定
flag的详细用法可参考flag包文档
flag包支持的命令行参数的类型有bool、int、int64、uint、uint64、float float64、string、duration
即布尔值、整型、浮点型、字符串、时间段类型
flag包命令行参数的定义
定义flag命令行参数,用来接收命 令行输入的参数值,一般有以下两种方法
- flag.TypeVar():先定义参数(实际上是指针),再定义
flag.TypeVar将命令行参数存储(绑定)到前面参数的值的指针(地址)
var name string
var age int
var height float64
var graduated bool
// &name 就是接收用户命令行中输入的-n后面的参数值
// 返回值是一个用来存储name参数的值的指针/地址
// 定义string类型命令行参数name,括号中依次是变量名、flag参数名、默认值、参数说明
flag.StringVar(&name, "n", "", "name参数,默认为空")
// 定义整型命令行参数age
flag.IntVar(&age,"a", 0, "age参数,默认为0")
// 定义浮点型命令行参数height
flag.Float64Var(&height,"h", 0, "height参数,默认为0")
// 定义布尔型命令行参数graduated
flag.BoolVar(&graduated,"g", false, "graduated参数,默认为false")
- flag.Type():用短变量声明的方式定义参数类型及变量名
// 定义string类型命令行参数name,括号中依次是flag参数名、默认值、参数说明
namePtr := flag.String("n", "", "name参数,默认为空")
// 定义整型命令行参数age
age := flag.Int("a", 0, "age参数,默认为0")
// 定义浮点型命令行参数height
height := flag.Float64("h", 0, "height参数,默认为0")
// 定义布尔型命令行参数graduated
graduated:= flag.Bool("g", false, "graduated参数,默认为false")
flag包命令行参数解析
固定用法,定义好参数后,通过调用flag.Parse()来对命令行参数进行解析写入注册的flag里,进而解析获取参数值,通过查看源码中也是调用的os.Args
源码路径go/src/flag/flag.go
// Parse parses the command-line flags from os.Args[1:]. Must be called
// after all flags are defined and before flags are accessed by the program.
func Parse() {
// Ignore errors; CommandLine is set for ExitOnError.
CommandLine.Parse(os.Args[1:])
}
进而查看Parse方法的源码
func (f *FlagSet) Parse(arguments []string) error {
f.parsed = true
f.args = arguments
for {
seen, err := f.parseOne()
if seen {
continue
}
if err == nil {
break
}
switch f.errorHandling {
case ContinueOnError:
return err
case ExitOnError:
if err == ErrHelp {
os.Exit(0)
}
os.Exit(2)
case PanicOnError:
panic(err)
}
}
return nil
}
真正解析参数的是parseOne方法(这里省略源码),结论是
- 当遇到单独的一个 "-" 或不是 "-" 开始时,会停止解析
- 遇到连续的两个 "-" 时,解析停止
- 在终止符"-"之后停止
解析参数时,对于参数的指定方式一般有"-"、"--"、以及是否空格等方式,组合下来有如下几种方式
| -flag xxx | 空格和一个-符号 |
|---|---|
| --flag xxx | 空格和两个-符号 |
| -flag=xxx | 等号和一个-符号 |
| --flag=xxx | 等号和两个-符号 |
其中,-flag xxx方式最为常用,如果参数是布尔型,只能用 等号方式指定
flag包命令行帮助
flag包默认会根据定义的命令行参数,在使用时如果不输入参数就打印对应的帮助信息
这样的帮助信息我们可以对其进行覆盖去改变默认的Usage
package main
import (
"flag"
"fmt"
)
func main() {
var host string
var port int
var verbor bool
var help bool
// 绑定命令行参数与变量关系
flag.StringVar(&host, "H", "127.0.0.1", "ssh host")
flag.IntVar(&port, "P", 22, "ssh port")
flag.BoolVar(&verbor, "v", false, "detail log")
flag.BoolVar(&help, "h", false, "help")
// 自定义-h
flag.Usage = func() {
fmt.Println(`
Usage: flag [-H addr] [-p port] [-v]
Options:
`)
flag.PrintDefaults()
}
// 解析命令行参数
flag.Parse()
if help {
flag.Usage()
} else {
fmt.Println(host, port, verbor)
}
}
/*
➜ go run flag_args.go -h
Usage: flag [-H addr] [-p port] [-v]
Options:
-H string
ssh host (default "127.0.0.1")
-P int
ssh port (default 22)
-h help
-v detail log
*/