命令行参数库 go-flags

903 阅读5分钟

简介

golang的命令行参数库经常用的有 spf13/pflagjessevdk/go-flagsurfave/clispf13/cobra这几个。

这次推荐的是go-flags,其特点如下:

  1. help usage显示合理,简介 、参数分组、命令,一个不少,如下:

1673754615242.png

  1. 组织时候使用结构体加上tag标签进行参数显示的控制。结构化的组织,比较方便使用和理解。

参数设定

这里用mysql数据库连接参数作为例子。代码如下


type MysqlCfg struct {
   DbName   string `long:"dbname" short:"d" description:"数据库名" default:"admin"`
   Host     string `long:"dhost" short:"t" description:"数据库地址" required:"true"`
   Password string `long:"password" short:"p" description:"密码"`
   User     string `long:"user" short:"u" description:"用户"````
   IsLocal  bool   `long:"islocal" description:"是否本地数据库"`

}

func main() {
   var cfg MysqlCfg

  
    _, err := flags.Parse(&cfg)

    if err != nil {
       return
    }

   bts, _ := json.Marshal(cfg)
   fmt.Println("获取参数:", string(bts))
}

先看"-h" 帮助文档显示内容。

1673873094530.png

显示格式 windows or linux

这里的帮助文档是windows格式, 该库默认显示格式就是windows格式。而要用linux格式显示则需要编译时候用-tags=forceposix

go build -tags=forceposix main.go

help文档变成如下格式。

1673873454513.png

一般大家都习惯linux格式显示,而不是window格式,所以在器issue上已经有很多人提出这个问题,相信不久,其默认格式会变成linux格式。

tag标签: 长参数名、短参数名、描述、默认值、必填项

tag标签最长用的就是标题里指出来的这几个,对比help显示结果,很容易知道各个tag作用。

long 表示完整的参数名,使用时候需要 --长参数名

short 表示短参数名,使用时候需要 -段参数名

description 表示参数的说明

default 设置参数的默认值,在help文档中会用会用括号括起来展示,如 -d --dbname 数据库名(default: admin)

required 设置必填项,如果不填写,则会报错。类似 "the required flag `-t, --dhost' was not specified"这样的提示内容。

bool值参数

该库支持的参数类型很多,包括基础类型,int、string、bool等也支持切片和map,甚至支持函数。但是大部分用到的参数还是基础类型。

其中bool值类型的参数有点特殊。

bool类型的参数,在使用时候不需要赋值,只需要填写,有则为true,没有则为false

./main.exe --islocal 这样的,其对应参数islocal参数为true

./main.exe 这样的,其对应参数islocal参数为false

./main.xex --islocal=false 这样的,其对应islocal参数依然是true,bool类型值得参数不需要显示指定值是true和false, 有则true没有则false,这点在help文档里也有表示,islocal其后没有冒号,表示不需要指定值。

_, err := flags.Parse(&cfg)

解析时候,除了required会因为缺少参数因为err外,当"-h" 帮助参数时候也会引发err,查看帮助文档,虽然不是错误,但是这样做很合理,因为每当"-h"时候并不是想要程序真的跑起来,只是想看看帮助文档。所以只好让"-h"参数去引发错误,以阻止程序继续运行。

参数分组

如果有多个参数,想按照功能给参数分个组,使用该库很容易办到。

比如这里有个mysql和redis得应用程序,需要获取mysql和reids得连接参数。其代码如下:

type MysqlCfg struct {
   DbName   string `long:"dbname" short:"d" description:"数据库名" default:"admin"`
   Host     string `long:"dhost" short:"t" description:"数据库地址"`
   Password string `long:"dbpassword" short:"p" description:"密码"`
   User     string `long:"dbuser" short:"u" description:"用户"`
}

type RedisCfg struct {
   Rname string `long:"redisname" description:"redis名称"`
   Host  string `long:"redishost" description:"redis地址"`
}

type Config struct {
   MysqlCfg MysqlCfg `group:"mysql"`
   RedisCfg RedisCfg `group:"redis"`
}

func main() {
   var cfg Config

   _, err := flags.Parse(&cfg)

   if err != nil {
      return
   }
   bts, _ := json.Marshal(cfg)
   fmt.Println("获取参数:", string(bts))
}

重点看config struct,分组配置使用group 标签名,气候设置得名称则为组名,其help文档如下

1673875207644.png

参数按照mysql和redis进行了分组,每个组都对应一个结构体。

命令参数

现在你做了一个服务,需要命令来启动服务,同时也需要初始化该服务数据库的命令,这时候就可用该库的命令参数功能了。代码如下。


var cfg Config

//数据库参数
type MysqlCfg struct {
   DbName   string `long:"dbname" short:"d" description:"数据库名" default:"admin"`
   Host     string `long:"dhost" short:"t" description:"数据库地址"`
   Password string `long:"dbpassword" short:"p" description:"密码"`
   User     string `long:"dbuser" short:"u" description:"用户"`
}

//初始化命令
type InitCmd struct {
   InitDb    bool `long:"initdb" description:"初始化数据库"`
   InitAdmin bool `long:"initadmin" description:"初始化超级管理员"`
}

func (this *InitCmd) Execute(args []string) error {
   if this.InitDb {
      bts, _ := json.Marshal(cfg.MysqlCfg)
      log.Println("初始化数据库:", string(bts))
   }
   if this.InitAdmin {
      bts, _ := json.Marshal(cfg.MysqlCfg)
      log.Println("初始化超级管理员:", string(bts))
   }

   return nil
}

//启动命令
type RunServe struct {
   ApiHost string `long:"apihost" description:"服务host"`
}

func (this *RunServe) Execute(args []string) error {
   log.Println("服务启动:", this.ApiHost)
   return nil
}

//参数
type Config struct {
   MysqlCfg MysqlCfg
   InitCmd  InitCmd  `command:"init" description:"初始化服务"`
   RunServe RunServe `command:"run" description:"启动服务"`
}

func main() {
   _, err := flags.Parse(&cfg)
   if err != nil {
      return
   }
}

先看一下help帮助文档

1673878221274.png

可以看到按照命令配置项

  1. 配置函数 execute
  2. 配置标签 command

配置完后帮助文档会显示命令相关内容。

再看看其中一个子命令的帮助文档会是什么样子?

1673878377386.png

可以看到会显示该子命令的相关参数,go-flags库做的很到位。

usage

usage里可以填写程序版本,说明等内容。

默认的usage显示的

程序名 [参数项] [子命令|子命令] [子命令参数项]

这种程序说明已经很到位了,不需要做改动,只需要添加其他说明即可

下面代码实现usage添加内容。

type config struct {
   AppName string `long:"appname"`
}

func main() {
   var opt config

   p := flags.NewParser(&opt, flags.Default)
   p.ShortDescription = "v1.2"
   p.LongDescription = ` v1.2
这是个吊炸天的程序
小心运行。。。。`
   _, err := p.Parse()
   if err == nil {
      fmt.Println("hello word " + opt.AppName)
   }
}

usage实现主要是flags.Parser对象上的一个属性,ShortDescription和LongDescription。见代码便知其意,看看效果。

1673879302939.png

结语

之前用过golang自带的flag,用过pflag,也用过大名鼎鼎的cobra,直到碰上了go-flags,才发现命令可以如此简单明了使用。虽然默认帮助文档格式不是linux,但是其他的设计都很合理。