为了账号安全,请及时绑定邮箱和手机立即绑定

Go标准库flag包的“小陷阱”

标签:
Java Python Go

图片描述

Go语言号称“自带电池(battery-included)”,这意味着Go标准库可开箱即用,为Gopher提供了功能丰富的常用工具包,足以应付多数日常开发所需。尤其在Go语言擅长的领域,Go标准库工具包更是有着广泛的应用。下图是[Go官方2020年用户调查]的结果:

图片描述

我们看到cli([command-line interface])领域开发占据了Go语言应用的Top2位置,仅次于开发API/RPC服务。而普通cli应用的开发总是离不开标准库的flag包。

flag包估计是很多gopher入门go语言的必经之路。flag使用起来十分简单,功能也不差,常规的命令行程序的flag形式它都支持,比如下面这个示例程序:

// flag_demo1.go
package main

import (
	"flag"
	"fmt"
)

var (
	n = flag.Int("n", 1234, "help message for flag n")
)

func main() {
	flag.Parse()
	fmt.Printf("n=%d\n", *n)
}

flag_demo1仅支持一个cmd flag: -n。我们可以像下面这样使用flag_demo1这个cli程序,为变量n传值:

$go build flag_demo1.go

$./flag_demo1          
n=1234 //默认值

$./flag_demo1 -n 1111
n=1111

$./flag_demo1 --n 1111
n=1111 // --n和-n是等价的

$./flag_demo1 -n=2222 
n=2222

$./flag_demo1 --n=2222
n=2222

我们看到,我们可以使用下面四种形式为一个整型flag变量传参数:

  • -n value
  • –n value
  • -n=value
  • –n=value

无论使用哪种形式,它们起到的效果是等价的。

但是当我们将flag放置在cli应用的最后面时,我们要小心了:

$./flag_demo1 show -n=2222
n=1234

我们看到虽然我们在命令行因公flag_demo1的参数列表中进行了-n=2222的参数传递,但flag_demo1的flag包直接无视了这次参数传递,而将变量n置为默认值1234了。

这是因为flag包的命令行参数的解析逻辑是:当碰到第一个非flag参数时,便停止解析。上面命令行执行时传入的“show”并非flag_demo1的flag参数,因此flag包就会在解析完show后停止后面命令行参数(-n=2222)的解析,于是上述命令行就等价于:

$./flag_demo1 show
n=1234

那么n=1234就不足为奇了!这被我称为flag包的第一个“小陷阱”。不仅像“show”这样的非flag参数可以阻断flag包对命令行参数列表的继续解析,单独存在的“-”和“–”也具有同样的“阻断功能”:

$./flag_demo1 -- -n=2222  
n=1234
$./flag_demo1 - -n=2222 
n=1234

我们也常在命令行flag参数中使用bool类的参数值,比如下面示例:

// flag_demo2.go

package main

import (
	"flag"
	"fmt"
)

var (
	n  = flag.Int("n", 1234, "int value for flag n")
	b1 = flag.Bool("b1", false, "bool value for flag b1")
	b2 = flag.Bool("b2", false, "bool value for flag b2")
)

func main() {
	flag.Parse()
	fmt.Printf("n=%d\n", *n)
	fmt.Printf("b1=%t\n", *b1)
	fmt.Printf("b2=%t\n", *b2)
}

这个示例中有两个bool型flag参数和一个int型flag参数,我们来运行一下该cli应用:

$go build flag_demo2.go
$./flag_demo2 -b1 true -b2 true -n 2222
n=1234
b1=true
b2=false

运行的输出似乎与预期结果不符啊!为什么b2变量的值依旧为false,变量n的值为啥不是2222?难道在多个flag参数下,flag包有bug?其实不是的!

问题就在于bool类型flag参数的特殊性。由于一些原因,bool类型flag参数不支持“-arg value”形式,只支持下面两种形式:

-arg
-arg=value

我们按bool类型flag参数的正确传递方法再运行一下上面的flag_demo2:

$./flag_demo2 -b1=true -b2=true -n 2222
n=2222
b1=true
b2=true

这回的输出与预期吻合。

但细心的朋友可能会发现,之前的错误用法:

$./flag_demo2 -b1 true -b2 true -n 2222

十分有迷惑性!因为变量b1的输出值是符合预期的,这让人误以为flag参数的传递方法是正确无误的。这种“错觉”让gopher不知不觉地掉入了**flag包的第二个“陷阱”**中。而上面的错误flag参数值传递实质上等价于:

$./flag_demo2 -b1

这就是为什么b1=true,而b2和n均为默认值的原因了!

flag包是我们日常最广泛使用的标准库包之一,因此务必了解flag包可能被误用的情况,别掉入flag包的“小陷阱”中!陷阱虽小,出事是大,希望这里的分享能帮助大家在日常绕过这些“陷阱”!


Go技术专栏“改善Go语⾔编程质量的50个有效实践”正在慕课网火热热销中!本专栏主要满足广大gopher关于Go语言进阶的需求,围绕如何写出地道且高质量Go代码给出50条有效实践建议,上线后收到一致好评!欢迎大家订
阅!

img{512x368}

我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网热卖中,欢迎小伙伴们订阅学习!

图片描述


点击查看更多内容
“小礼物走一走,来慕课关注我”
赞赏支持
Tony Bai 说 去围观
Tony Bai,智能网联汽车独角兽公司先行研发部负责人,Go语言专家,资深架构师,《Go语言精进之路》作者。
评论

作者其他优质文章

正在加载中
全栈工程师
手记
粉丝
7757
获赞与收藏
477

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消