让flag支持从文件中读取命令行参数

共 3187字,需浏览 7分钟

 ·

2021-11-30 23:43

golang标准库提供了flag包来处理命令行参数。常规的使用都是在命令行中启动服务的时候一一的输入,让程序解析。今天给大家介绍一种可以从文件中读取命令行参数的实现方法。


  01 flag的常规应用
下面我们通过代码来演示下flag的常规应用。如下代码:
var (  RedisAddress string)
func init() { flag.StrVar(&RedisAddress, "redis_address", "127.0.0.1", "this is redis address")}
func main() { flag.Parse() if RedisAddress != "" { //redis初始化操作 } fmt.Printf("redis address:%s\n", RedisAddress)}

然后在命令行中进行编译或直接运行时要指定-redis_address参数,如下:
go run main.go -redis_address=redisaddr.goxuetang.com

随着项目规模的增大,需要的命令行参数越来越多,假设有50个命令行参数甚至更多,如果我们一个一个指定的话,可想而知会是一件多么可怕的事情:参数多,难以维护,容易出错。下面我们就介绍通过让程序从配置文件中读取的方法。


02 通过文件读取命令行参数的flag应用
常规应用中,我们看到,读取并解析命令行参数的逻辑主要在flag.Parse中。我们对flag.Parse()进一步查看,看到源码包中flag.Parse()函数实际上是调用了CommandLine.Parse(arguments []string) error函数,如下:
func Parse() {  // Ignore errors; CommandLine is set for ExitOnError.  CommandLine.Parse(os.Args[1:])}
通过上面代码可知,os.Args[1:]就是命令行后跟的所有参数的集合(在上面的例子中就是 [-redis_address=redisaddr.goxuetang.com]),然后CommandLine.Parse对该字符串集合进行实际的解析。
那我们要实现的目标实际上就是将文件中的每一行读取出来,组织成CommandLine.Parse函数可接收的参数即可。如下图所示flag常规解析和读取文件方式的示意图:


好了,思路讲清楚后,我们来看下代码实现

03 代码实现
我们将实现的函数封装在flagx的包中,本文意图是讲解实现的思路,所以在代码中忽略了错误处理,大家在实际项目中自行添加即可。
package flagx
//存储命令行传过来的文件路径var FlagFile string
func init() { //注册命令行的flagfile参数 flag.Var(&FlagFile, "flagfile", "")}
//在Parse函数中调用,将解析到的命令行参数打印出来func visitFlag(f *flag.Flag) { fmt.Println(f.Name + "=" + f.Value.String())}
func Parse() error { //先解析命令行中的-flagfile参数 flag.Parse()
var validFlagLines []string
flagContents, _ := ioutil.ReadFile(FlagFile)
configContent := string(flagContents) // 统一使用\n作为换行符,以便后面按分隔符分隔字符串成切片  configContent = strings.Replace(configContent, "\r\n""\n"-1) flagLines := strings.Split(configContent, "\n") for _, line := range flagLines { //忽略掉以 # 开头的注释行 if string([]rune(line)[0]) != "#" { //将每一行作为一个有效的命令行参数 validFlagLines = append(validFlagLines, line) } }
//实际执行解析命令行参数的地方,这里就又和常规的flag调用一样了  _ := flag.CommandLine.Parse(validFlagLines)  //将解析到的命令行参数都按visitFlag函数的格式输出 flag.VisitAll(visitFlag) return nil}

假设命令行参数文件存在于文件/data/conf/prod.gflags中,内容如下:
# redis地址-redis_address=redisaddr.goxuetang.com# redis端口-redis_port=9999
# 其他所有的命令行参数

好,接下来写个main函数测试一下,main函数中引入的gotech.github.com/m/flagfile/flagx 包是我项目下的定义路径,大家在实际开发中根据自己的项目组织包路径即可。

package mainimport (  "flag"  "fmt"  "gotech.github.com/m/flagfile/flagx")var (  RedisAddress string  RedisPort    int)
func init() { flag.StringVar(&RedisAddress, "redis_address", "127.0.0.1", "this is redis address") flag.IntVar(&RedisPort, "redis_port", 6379, "this is redis port")}
func main() { //这里调用我们自定实现的Parse函数 err := flagx.Parse()  fmt.Println("err:", err)
if RedisAddress != "" && RedisPort != 0 { //redis初始化操作 } fmt.Printf("redis address:%s,port:%d\n", RedisAddress, RedisPort)}

执行如下命令
go run main.go -flagfile=/data/conf/prod.gflags


04 总结
和常规的flag应用相比,将命令行参数写在配置文件中,可以提高命令行参数的可读性以及可维护性。该方法的实现思路主要是应用了flag.Parse解析命令行参数底层的CommandLine.Parse(arguments []string)的函数功能,将文件中的每行命令行参数组织成一个切片,然后调用CommandLine.Parse方法。

推荐阅读


福利

我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。


浏览 67
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报