问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

Golang标准库flag全面讲解

发布网友 发布时间:2024-09-27 18:11

我来回答

1个回答

热心网友 时间:2024-09-28 00:49

文章首发于公众号:程序员读书;欢迎关注,可以第一时间收到文章更新哦,转载本文请注明来源!

前言

今天来聊聊Go语言标准库中一个非常简单的库flag,这个库的代码量只有1000行左右,却提供了非常完善的命令行参数解析功能。

命令行参数

如果你有使用过类Unix(比如MacOS,Linux)等操作系统,相信你应该明白命令参数是什么,比如下面的两条命令:

$ mysql -u root -p 123456$ ls -al

第一条命令是MySQL的客户端,其-u root和-p 123456就是命令行参数,第二条命令用于显示当前目录的文件及目录,该命令中-al就是命令行参数。

flag库的作用就是帮我们将命令后面的选项参数解析到对应的变量中。

使用详解

要了解一个库,须从使用开始,下面我们通过一个简单的示例来快速了解flag库的使用,这个示例可以接收从命令行传递的用于连接数据库的参数,代码如下:

package mainimport ("flag""fmt")var (host stringdbName stringport intuser stringpassword string)func main() {flag.StringVar(&host, "host", "", "数据库地址")flag.StringVar(&dbName, "db_name", "", "数据库名称")flag.StringVar(&user, "user", "", "数据库用户")flag.StringVar(&password, "password", "", "数据库密码")flag.IntVar(&port, "port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", host)fmt.Printf("数据库名称:%s\n", dbName)fmt.Printf("数据库用户:%s\n", user)fmt.Printf("数据库密码:%s\n", password)fmt.Printf("数据库端口:%d\n", port)}

在命令行窗口输入以下命令,开始运行程序

go run main.go -host=localhost -user=test -password=123456 -db_name=test -port=3306

运行结束,输出结果如下所示:

数据库地址:localhost数据库名称:test数据库用户:test数据库密码:123456数据库端口:3306

上面的示例就是一个解析命令行选项参数的模板,包括下面三个步骤:

定义好接收参数的变量。

调用flag.StringVar()等函数将命令行选项与变量绑定。

调用flag.Parse()函数,开始解析变量。

在上面程序中,我们用了StringVar函数绑定字符串类型的参数,用了IntVar函数绑定整数类型的参数,除了字符串和整型,flag支持boolean,Duration,float64,Int64,uint,uint64等类型,下面是这些函数的定义,用法与StringVar相同。

func BoolVar(p *bool, name string, value bool, usage string)func DurationVar(p *time.Duration, name string, value time.Duration, usage string)func Float64Var(p *float64, name string, value float64, usage string)func Int64Var(p *int64, name string, value int64, usage string)func IntVar(p *int, name string, value int, usage string)func StringVar(p *string, name string, value string, usage string)func Uint64Var(p *uint64, name string, value uint64, usage string)func UintVar(p *uint, name string, value uint, usage string)

上面列出的函数带有Var后缀,表示需要我们自己传递一个变量去接收命令行参数,而flag在这些函数有基础上,封装了下面列表的函数,这些函数没有Var后缀,跟上面的函数相比少了一个参数,却多了一个返回值,这个返回值就是接收命令参数的变量指针。

func Bool(name string, value bool, usage string) *boolfunc Duration(name string, value time.Duration, usage string) *time.Durationfunc Float64(name string, value float64, usage string) *float64func Int(name string, value int, usage string) *intfunc Int64(name string, value int64, usage string) *int64func String(name string, value string, usage string) *stringfunc Uint(name string, value uint, usage string) *uintfunc Uint64(name string, value uint64, usage string) *uint64

所以我们把上面的示例改写为以下的样子:

package mainimport ("flag""fmt")func main() {host := flag.String("host", "", "数据库地址")dbName := flag.String("db_name", "", "数据库名称")user := flag.String("user", "", "数据库用户")password := flag.String("password", "", "数据库密码")port := flag.Int("port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", *host)fmt.Printf("数据库名称:%s\n", *dbName)fmt.Printf("数据库用户:%s\n", *user)fmt.Printf("数据库密码:%s\n", *password)fmt.Printf("数据库端口:%d\n", *port)}

另外,运行程序时,在后面跟上-h或--help来查看命令的参数选项,如:

go run main.go --helpUsage of main:-db_name string数据库名称-host string数据库地址-password string数据库密码-port int数据库端口 (default 3306)-user string数据库用户选项语法

flag支持以下三种命令行格式,参数前面的-也可以换成--,在flag库中,--并不是表示长选项的意思。

cmd -flagcmd -flag=xcmd -flag x

第一种只用布尔值的选项,如果该参数出现,则为true,不出则为默认值,而其他数据类型不能使用这种格式传值。

第二种可适用任何类型,因此也是最常用的格式。

第三种不可用于布尔值的选项。

flag在解析参数时,如果遇到第一个非选项参数(不是以-或--开头的)或终止符--,就会停止解析,比如上面的示例中,我们将运行命令改成下面的样子:

go run main.go -host=localhost noflag -user=test -password=123456 -db_name=test -port=3306

运行结果如下,可以看到解析-host参数之后遇到了noflag这样的非选项参数,flag就停止解析了,所以后面的参数都只输出了默认值。

package mainimport ("flag""fmt")var (host stringdbName stringport intuser stringpassword string)func main() {flag.StringVar(&host, "host", "", "数据库地址")flag.StringVar(&dbName, "db_name", "", "数据库名称")flag.StringVar(&user, "user", "", "数据库用户")flag.StringVar(&password, "password", "", "数据库密码")flag.IntVar(&port, "port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", host)fmt.Printf("数据库名称:%s\n", dbName)fmt.Printf("数据库用户:%s\n", user)fmt.Printf("数据库密码:%s\n", password)fmt.Printf("数据库端口:%d\n", port)}0

整数类型的参数可以接收十进制、八进制,十六进制的参数,布尔型可以接收下面列出参数

package mainimport ("flag""fmt")var (host stringdbName stringport intuser stringpassword string)func main() {flag.StringVar(&host, "host", "", "数据库地址")flag.StringVar(&dbName, "db_name", "", "数据库名称")flag.StringVar(&user, "user", "", "数据库用户")flag.StringVar(&password, "password", "", "数据库密码")flag.IntVar(&port, "port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", host)fmt.Printf("数据库名称:%s\n", dbName)fmt.Printf("数据库用户:%s\n", user)fmt.Printf("数据库密码:%s\n", password)fmt.Printf("数据库端口:%d\n", port)}1

Duration类型的参数接收可以被time.ParseDuration()解析的参数。

flag是怎么解析参数的?

我们知道flag库是用于命令行解析的,但其内部是怎么解析的呢?下面我们来分析一下

一个命令行参数包含以下四个部分:

接收参数的变量

参数名称

默认值

参数说明

所以flag设置命令行参数的函数有四个参数,比如:

package mainimport ("flag""fmt")var (host stringdbName stringport intuser stringpassword string)func main() {flag.StringVar(&host, "host", "", "数据库地址")flag.StringVar(&dbName, "db_name", "", "数据库名称")flag.StringVar(&user, "user", "", "数据库用户")flag.StringVar(&password, "password", "", "数据库密码")flag.IntVar(&port, "port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", host)fmt.Printf("数据库名称:%s\n", dbName)fmt.Printf("数据库用户:%s\n", user)fmt.Printf("数据库密码:%s\n", password)fmt.Printf("数据库端口:%d\n", port)}2

flag内部有一个名称CommandLine的变量,其类型为FlagSet,如:

package mainimport ("flag""fmt")var (host stringdbName stringport intuser stringpassword string)func main() {flag.StringVar(&host, "host", "", "数据库地址")flag.StringVar(&dbName, "db_name", "", "数据库名称")flag.StringVar(&user, "user", "", "数据库用户")flag.StringVar(&password, "password", "", "数据库密码")flag.IntVar(&port, "port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", host)fmt.Printf("数据库名称:%s\n", dbName)fmt.Printf("数据库用户:%s\n", user)fmt.Printf("数据库密码:%s\n", password)fmt.Printf("数据库端口:%d\n", port)}3

FlagSet就是一个命令行参数的集合体,当我们调用诸如IntVar这类的函数时,就是将命令行的默认值、参数说明,参数名称,接收参数的变量等信息告诉flag库,而flag内部会让CommandLine来处理,用这些信息创建Flag类型的变量,将添加到这个集合体中。

package mainimport ("flag""fmt")var (host stringdbName stringport intuser stringpassword string)func main() {flag.StringVar(&host, "host", "", "数据库地址")flag.StringVar(&dbName, "db_name", "", "数据库名称")flag.StringVar(&user, "user", "", "数据库用户")flag.StringVar(&password, "password", "", "数据库密码")flag.IntVar(&port, "port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", host)fmt.Printf("数据库名称:%s\n", dbName)fmt.Printf("数据库用户:%s\n", user)fmt.Printf("数据库密码:%s\n", password)fmt.Printf("数据库端口:%d\n", port)}4

最后,当我们调用flag.Parse函数时,实际就是调用FlagSet结构体的Parse函数将命令参数解析到变量中,flag.Parse函数代码如下:

package mainimport ("flag""fmt")var (host stringdbName stringport intuser stringpassword string)func main() {flag.StringVar(&host, "host", "", "数据库地址")flag.StringVar(&dbName, "db_name", "", "数据库名称")flag.StringVar(&user, "user", "", "数据库用户")flag.StringVar(&password, "password", "", "数据库密码")flag.IntVar(&port, "port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", host)fmt.Printf("数据库名称:%s\n", dbName)fmt.Printf("数据库用户:%s\n", user)fmt.Printf("数据库密码:%s\n", password)fmt.Printf("数据库端口:%d\n", port)}5

从上面的代码我们也可以看出来,FlagSet的Parse函数最终是通过获取os.Args数组的数据来解析命令行参数的。

即然我们知道flag是通过类型为FlagSet的变量CommandLine来处理命令行参数的,那其实我们也可以自己创建一个FlagSet类型的变量来处理命令行参数,所以我们可以将上面的例改成下面的样子:

package mainimport ("flag""fmt")var (host stringdbName stringport intuser stringpassword string)func main() {flag.StringVar(&host, "host", "", "数据库地址")flag.StringVar(&dbName, "db_name", "", "数据库名称")flag.StringVar(&user, "user", "", "数据库用户")flag.StringVar(&password, "password", "", "数据库密码")flag.IntVar(&port, "port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", host)fmt.Printf("数据库名称:%s\n", dbName)fmt.Printf("数据库用户:%s\n", user)fmt.Printf("数据库密码:%s\n", password)fmt.Printf("数据库端口:%d\n", port)}6

另外,我们已经知道了flag解析参数的来源是os.Args这样的字符串数组,那我们也可以模拟一个这样的数组,将数组解析到变量之中,而不需要去解析os.Args数组,下面的例子就是这样做的:

package mainimport ("flag""fmt")var (host stringdbName stringport intuser stringpassword string)func main() {flag.StringVar(&host, "host", "", "数据库地址")flag.StringVar(&dbName, "db_name", "", "数据库名称")flag.StringVar(&user, "user", "", "数据库用户")flag.StringVar(&password, "password", "", "数据库密码")flag.IntVar(&port, "port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", host)fmt.Printf("数据库名称:%s\n", dbName)fmt.Printf("数据库用户:%s\n", user)fmt.Printf("数据库密码:%s\n", password)fmt.Printf("数据库端口:%d\n", port)}7

运行程序,在命令后面不需要跟命令行参数,如下:

package mainimport ("flag""fmt")var (host stringdbName stringport intuser stringpassword string)func main() {flag.StringVar(&host, "host", "", "数据库地址")flag.StringVar(&dbName, "db_name", "", "数据库名称")flag.StringVar(&user, "user", "", "数据库用户")flag.StringVar(&password, "password", "", "数据库密码")flag.IntVar(&port, "port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", host)fmt.Printf("数据库名称:%s\n", dbName)fmt.Printf("数据库用户:%s\n", user)fmt.Printf("数据库密码:%s\n", password)fmt.Printf("数据库端口:%d\n", port)}8

运行后结果如下:

package mainimport ("flag""fmt")var (host stringdbName stringport intuser stringpassword string)func main() {flag.StringVar(&host, "host", "", "数据库地址")flag.StringVar(&dbName, "db_name", "", "数据库名称")flag.StringVar(&user, "user", "", "数据库用户")flag.StringVar(&password, "password", "", "数据库密码")flag.IntVar(&port, "port", 3306, "数据库端口")flag.Parse()fmt.Printf("数据库地址:%s\n", host)fmt.Printf("数据库名称:%s\n", dbName)fmt.Printf("数据库用户:%s\n", user)fmt.Printf("数据库密码:%s\n", password)fmt.Printf("数据库端口:%d\n", port)}9自定义数据类型

如果flag提供的数据类型不能满足我们的需要,我们也可以自定义类型,自定义类型需要实现flag中的Value接口,该接口定义如下:

go run main.go -host=localhost -user=test -password=123456 -db_name=test -port=33060

Value类

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
蓝靛果忍冬地理分布 盆栽种植蓝靛果注意事项 中国藏书印欣赏 藏书印一般刻什么字 ...具备拆迁资格的,开发商签订的拆迁安置房,补偿协议有效吗? 网络工程师要考哪些科目 初级网络工程师考什么 我24岁了还能长高吗,我现在看来是个娃娃脸,好多人都以为我在读高中... 在TP-LINK 54M的路由器上的复位键按了10秒 有wifi 电脑没有无线网卡 怎么让电脑通过数据线连接手机上网 ...错的人 却可能是你一生最难忘的人”这首歌叫什么名字? go语言如何运行(go语言技巧) golang执行命令行(go执行命令行) 电脑开机后, CPU频率一直在0.0几? 国家司法考试一般考多少门?总分是多少? 女儿十一岁了,妈妈说了女儿不听话,就住在了爷爷奶奶家你说怎么 孩子11岁叛逆期怎么管教最合适 11岁孩子叛逆期怎么管教 十一岁的小孩不听话怎么办十一岁的小孩不听话的方法 教育十一二岁的孩子如何管教 教育十一二岁的孩子怎么管教 教育十一二岁的孩子怎么管教教育十一二岁的孩子如何管教 出世什么意思 2019年新疆喀什社保缴存基数 2019新疆喀什警察报到要培训吗? 2019年新疆内初班喀什分数线是多少? 2019新疆维吾尔自治区喀什地区塔什库尔干县区内初中班考试成绩 准考 ... 浙江省非遗有哪些著名的 闽南语“ 特直”是什么意思 ...processorlnfmation(KERNEL32.dll)是什么意思? InternetSeer来的邮件什么意思? Go语言 通过go bulid -tags 实现编译控制 goprintfprintln go语言println和printf和print? 个体户可以申请注册商标吗 个体户能申请注册商标吗 云南大学洋浦校区 属于哪个城市 我的出脚频率非常慢 怎么办?高分奖励!!如果好 还会追加 电视版的快手怎么安装 海信电视怎么样下载快手? 从洋浦车场到昆明理工大学城市学院如何坐车? 为啥win10不能安装 如何发送父亲去世的消息给亲人朋友 安装Windows10时出现错误,怎么回事? 安装Windows10时出错 昆山城东游泳馆推荐,昆山户外团建旅游景点 i39100和i310100有什么区别? 苏州宝藏级别的性价比团建民宿都有哪些? 原汁机使用安全吗,会不会有危险? 惠人和nuc原汁机哪个好 supersu是什么? 2024高家庄生态园门票价格