本文主要是介绍go Flag包和cobra库的使用。
cli全程为(Command line interfaces,命令行接口),主要用于命令行操作。因为go的特殊性(不需要安装任何的库和环境,直接会编译好目标系统的二进制文件),很适合用来做这个功能,比如Java就需要安装环境,比较麻烦,Java里面也有比较好的框架(Spring-Shell)。
它是go的标准库提供的一个包,实现了命令行标志的解析功能。
flag包中命令格式如下:
命令 标志 标志对应的值 参数
example如下:
kla(命令) -f /opt/module/words/config/dev.yaml(标志) param1 param2(参数)
比如命令行中规定-file
来指定文件,flag包会帮助我们来做解析。将-f
指定的参数解析出来,而不需要我们来做解析。此外还可以获取到传递的参数。
Code:
package main
import "flag"
func main() {
s := flag.String("file", "", "config path")
flag.Parse()
println(*s)
}
下面来介绍一下flag包的一些使用。
在使用flag的时候,一般来说,有两个步骤
flag.parse()
解析。flag包提供了下面几个类型的flag声明方法,
// 需要注意方法的返回值,返回的是指针
flag.String("string", "", "string")
flag.Uint64("uint64",0,"uint64")
flag.Uint("uint",0,"uint")
flag.Float64("Float64",0,"Float64")
flag.Duration("Duration",time.Hour,"Duration")
flag.TextVar(nil,"test",nil)
此外,几个方法,他们和上述方法的差别是可以接收指针,比如:
// 返回的是指针
var nFlag = flag.Int("n", 1234, "help message for flag n")
// Var结尾的方法可以接收指针,没有返回值
var flagvar int
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
还可以给声明的flag绑定函数
var ip string
// 指定函数,自定义解析
flag.Func("ip","ip", func(s string) error {
ip1,err := netip.ParseAddr(s)
ip = ip1.String()
return err
})
flag.Parse()
println(ip)
还可以自定义类型来做转换
需要自己定义参数类型,该类型必须实现Value
接口。
可以点看上述的那几个方法比如,StringVar看,还是自定义了一个stringValue
类型,并且实现了Value接口,底下调用的是Var
方法
flag.Var(nil,"-c","自定义类型,需要实现Value接口")
Value接口如下:
type Value interface {
String() string
// 将传递进来的参数,做自定义的处理
Set(string) error
}
flag包允许下面的几个传递参数的方法
-flag
--flag // double dashes are also permitted
-flag=x
-flag x // non-boolean flags only
Integer flags accept 1234, 0664, 0x1234 and may be negative. Boolean flags may be:
int的flag可以接收1234, 0664, 0x1234和负数
,bool类型的参数可以是下面中的一个
1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False
Duration的标志可以接收任何被time.ParseDuration
可以解析的字符串
有两个访问的方法
// 访问所有的已经被设置值的flag
flag.Visit(func(f *flag.Flag) {
println(f.Name)
})
// 访问所有的的flag,包括已被设置值和没有设置值的flag
flag.VisitAll(func(f *flag.Flag) {
println(f.Name)
})
可以通过lookup来查找Flag
// 传递标志的名字返回Flag的指针对象,通过flag可以获取到Flag的名字,Value,defaultValue
func Lookup(name string) *Flag
arg的函数如下:
代码:
package main
import (
"flag"
"fmt"
)
func main() {
var configPath string
flag.StringVar(&configPath,"f", "/opt/module/config/dev.yaml", "config path")
flag.Parse()
fmt.Printf("config path: %s \n",configPath)
// 获取所有的参数
fmt.Printf("args: %v \n",flag.Args())
// 按照数组下标来获取
fmt.Printf("arg: %s \n",flag.Arg(1))
// 参数数量
fmt.Printf("agr number: %d \n",flag.NArg())
}
需要清楚,flag包只是来做解析,将输入的数据(-f configPath param) 这样的数组做解析,flag包还支持不同的源,从这些源来做解析。
代码:
func main() {
// 自定义flagSet
// flag.ExitOnError异常处理枚举值
flagSet := flag.NewFlagSet("demo", flag.ExitOnError)
// 替换flag包中默认创建的FlagSet
flag.CommandLine = flagSet
// 正常的声明flag
s := flag.String("f", "name", "name")
// 将参数传递过去
err := flagSet.Parse([]string{"-f", "configPath","param1","param2"})
if err != nil {
panic(err)
}
println(*s)
println(flag.NArg())
}
下面解释一下flag包
CommandLine
,他是一个FlagSet对象,它关联的数据源默认是从os.args[0]中读取数据的。如果看flag.parse()
方法,其实调用的是默认的CommandLine的Parse方法
func Parse() {
// 调用的的还是FlagSet的Parse的方法(os.args第一个参数是命令的名字,第二个开始就是参数了)
CommandLine.Parse(os.Args[1:])
}
关于Flag
包就介绍到这里了,Flag包提供的功能对于Clis来说只能说是够用,但想要功能强大,还需要自己在下功夫。对于Clis有很多的专业的库来做。
看这个文档
https://go.dev/solutions/clis。
下面介绍一下cobra库的使用。
简而言之,很好用的Clis的库。
创建go项目,下载库
go get -u github.com/spf13/cobra/cobra
有两种方式,一种是自己写,一种是通过cobra-cli
来生成。
这里我采用的是 cobra-cli 来生成。
go install github.com/spf13/cobra-cli@latest
安装完成之后输入 cobra-cli
可以看到一下内容就说明安装好了
先创建一个项目,写好mod之后,执行下面的命令
cobra-cli init .
它会初始化一个cobra的一个初始化项目。也可以执行cobra-cli init -h
来查看帮助信息
初始化的项目在简单了,将上面的rootCmd中的Use修改为kla
,并且给他增加一个子命令 start。
cobra-cli add start
在看项目结构
重新编译运行项目结果如下:
运行子命令
发现,比起上面的flag,这感觉已经很好了。从这个例子中可以看到,它会自动生成help信息,并且很丰富,支持父子命令和flag。
cobra是一个cli的框架,利用他可以创建强大的现代化的CLI的应用程序并且可以生成命令文件,他包含一下特点
概念:
三个概念,命令(command),参数(args),Flags
最好的语法模式读起来就像句子一样,遵从下面的语法模式
appname 动词 名词 --形容词 或 appname 命令 参数 --Flag
比如:
hugo server --port=1313
git clone URL --bare
etcdctl get age --discovery-srv=127.0.0.1:2379
从一个例子来详细的了解它的使用方式
kla 有两个子命令,start和stop,并且在start和stop的时候可以支持一些flag
kla start 支持的flag如下:
- f 配置文件的路径
- ip 主机的ip
- port 主机的port
- namespace 名称空间
- username
- password
还可以传递参数,并且要求如果指定了username就必须指定password
kla stop 支持的flag如下:
- f 配置文件的路径
- retry-delay 重试间隔
ps:别在意这里的逻辑是否正确,这逻辑肯定是错误的,旨在说明情况
stop命令最少传递两个参数,并且start和stop都需要指定f
在开始,需要定义rootCommand,作为根命令,之后声明的子命令,start,stop
都是他的子命令,在每个子命令声明完自己的flag之后,在init函数中将其添加到rootCmd中,cobra推荐的文件结构如下::
上面例子写的代码如下:
main.go
func main() {
cmd.Execute()
}
root.go
package cmd
import (
"os"
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var (
rootCmd = &cobra.Command{
Use: "kla",
Short: "kla就是一个测试",
Long: `晋太元中,武陵人捕鱼为业。缘溪行,忘路之远近。忽逢桃花林,夹岸数百步,中无杂树,芳草鲜美,落英缤纷。渔人甚异之,复前行,欲穷其林。
林尽水源,便得一山,山有小口,仿佛若有光。便舍船,从口入。初极狭,才通人。复行数十步,豁然开朗。土地平旷,屋舍俨然,有良田、美池、桑竹之属。阡陌交通,鸡犬相闻。其中往来种作,男女衣着,悉如外人。黄发垂髫,并怡然自乐。`,
// 下面的注释是此命令关联的动作,此命令作为根,不需要动作,所有的动作都是在子命令上绑定的
// Run: func(cmd *cobra.Command, args []string) { },
}
configFilePath string
)
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.PersistentFlags().StringVar(&configFilePath,"f","","configPath")
// 将一个持久性的flag表示为必填,持续的意思是在他的子命令里面都有这个flag
// flag有两种,本地和持久
// 1. 本地(local)只属于此命令
// 2. 持续(Persistent)属于此命令及其子命令
rootCmd.MarkPersistentFlagRequired("f")
}
start.go
/*
Copyright © 2022 NAME HERE
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// startCmd represents the start command
var (
startCmd = &cobra.Command{
Use: "start", // 命令名字
Short: "start kla", // 短一点的描述
Long: `这就是一个用来测试的,start 命令可以启动kla,
一个尝尝的描述信息`, // 详细描述
Aliases: []string{"run"}, // 别名
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("启动kla,args:%v\n",args)
fmt.Printf("ip:%s\n",ip)
fmt.Printf("port:%d\n",port)
fmt.Printf("namespace:%s\n",namespace)
fmt.Printf("username:%s\n",username)
fmt.Printf("password:%s\n",password)
fmt.Printf("f:%s\n",configFilePath)
},
}
ip string
port int
namespace string
username string
password string
)
func init() {
rootCmd.AddCommand(startCmd)
startCmd.Flags().StringVar(&ip,"ip","127.0.0.1","ip地址")
startCmd.Flags().IntVarP(&port,"port","p",8000,"port")
startCmd.Flags().StringVarP(&namespace,"namespace","n","default","namespace")
startCmd.Flags().StringVar(&username,"username","admin","username")
startCmd.Flags().StringVar(&password,"password","admin","password")
startCmd.MarkFlagsRequiredTogether("username","password") // 表示有userName就必须得有password,他俩绑定在一起了
}
stop.go
/*
Copyright © 2022 NAME HERE
*/
package cmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
)
// stopCmd represents the stop command
var (
stopCmd = &cobra.Command{
Use: "stop",
Short: "stop kla",
Long: `这是一个很详细的描述
用他可以来描述stop命令的详细试用`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("stop called,args:%v\n", args)
fmt.Printf("retryDelay:%v\n", retryDelay)
fmt.Printf("f:%s\n", configFilePath)
},
// 参数校验,最少需要两个参数
Args: cobra.MinimumNArgs(2),
}
retryDelay time.Duration
)
func init() {
rootCmd.AddCommand(stopCmd)
stopCmd.Flags().DurationVarP(&retryDelay,"retry-delay","d",time.Second*10,"重试间隔时间")
}
在命令行运行,编译
go build -o kla
运行kla
-> % ./kla -h
晋太元中,武陵人捕鱼为业。缘溪行,忘路之远近。忽逢桃花林,夹岸数百步,中无杂树,芳草鲜美,落英缤纷。渔人甚异之,复前行,欲穷其林。
林尽水源,便得一山,山有小口,仿佛若有光。便舍船,从口入。初极狭,才通人。复行数十步,豁然开朗。土地平旷,屋舍俨然,有良田、美池、桑竹之属。阡陌交通,鸡犬相闻。其中往来种作,男女衣着,悉如外人。黄发垂髫,并怡然自乐。
Usage:
kla [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
// 上面两个是自动帮我们添加的
// 下面两个是我们自己的子命令
start start kla
stop stop kla
Flags:
--f string configPath
-h, --help help for kla
Use "kla [command] --help" for more information about a command.
查看start命令的帮助信息
-> % ./kla start -h
这就是一个用来测试的,start 命令可以启动kla,
一个尝尝的描述信息
Usage:
kla start [flags]
Aliases:
start, run
Flags:
-h, --help help for start
--ip string ip地址 (default "127.0.0.1")
-n, --namespace string namespace (default "default")
--password string password (default "admin")
-p, --port int port (default 8000)
--username string username (default "admin")
Global Flags:
--f string configPath
查看stop的帮助信息
-> % ./kla stop -h
这是一个很详细的描述
用他可以来描述stop命令的详细试用
Usage:
kla stop [flags]
Flags:
-h, --help help for stop
-d, --retry-delay duration 重试间隔时间 (default 10s)
Global Flags:
--f string configPath
没有指定f必传参数
-> % ./kla start param1 param2
Error: required flag(s) "f" not set
Usage:
kla start [flags]
Aliases:
start, run
Flags:
-h, --help help for start
--ip string ip地址 (default "127.0.0.1")
-n, --namespace string namespace (default "default")
--password string password (default "admin")
-p, --port int port (default 8000)
--username string username (default "admin")
Global Flags:
--f string configPath
指定f必传参数,正常运行
-> % ./kla start param1 param2 --f configPath1
启动kla,args:[param1 param2]
ip:127.0.0.1
port:8000
namespace:default
username:admin
password:admin
f:configPath1
指定username,没password
-> % ./kla start --f configpath1 --username lalal
Error: if any flags in the group [username password] are set they must all be set; missing [password]
Usage:
kla start [flags]
Aliases:
start, run
Flags:
-h, --help help for start
--ip string ip地址 (default "127.0.0.1")
-n, --namespace string namespace (default "default")
--password string password (default "admin")
-p, --port int port (default 8000)
--username string username (default "admin")
Global Flags:
--f string configPath
-> % ./kla stop param1 param2
Error: required flag(s) "f" not set
Usage:
kla stop [flags]
Flags:
-h, --help help for stop
-d, --retry-delay duration 重试间隔时间 (default 10s)
Global Flags:
--f string configPath
-> % ./kla stop --f configpath1
Error: requires at least 2 arg(s), only received 0
Usage:
kla stop [flags]
Flags:
-h, --help help for stop
-d, --retry-delay duration 重试间隔时间 (default 10s)
Global Flags:
--f string configPath
-> % ./kla stop param1 param2 --f configpath1 -d 20s
stop called,args:[param1 param2]
retryDelay:20s
f:configpath1
-> % ./kla stap
Error: unknown command "stap" for "kla"
Did you mean this?
start
stop
Run 'kla --help' for usage.
在之前的基础上增加一个子命令:myhelp
/*
Copyright © 2022 NAME HERE
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// myhelpCmd represents the myhelp command
var myhelpCmd = &cobra.Command{
Use: "myhelp",
Short: "自定义help",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("myhelp called")
},
}
func init() {
rootCmd.AddCommand(myhelpCmd)
//设置自己的help信息
myhelpCmd.SetHelpTemplate("这是我自己的help")
}
因为在根命令上声明了必要flag,所以在cobra自动添加的命令也会有,并且还是必须的。也就是说,在运行上面的help,completion的时候都需要指定f
上面的例子已经包含了他的日常使用,下面对一些方法说明一下:
有两种flag
不同的类型下面各有三种方式
startCmd.Flags().String("ip","127.0.0.1","ip地址") // 返回指针
startCmd.Flags().StringVar(&ip,"ip","127.0.0.1","ip地址") // flag 是 --开头,比如这里就是 --ip
startCmd.Flags().StringVarP(&ip,"ip","p","127.0.0.1","ip地址") // 支持短名, 比如这里 可以是--ip,也可以是 -p
lookup := startCmd.Flags().Lookup("ip") // 可以通过lookUp查找,需要注意的是本地的在 flags里面查找,持续性的在PersistentFlags()里面查找
在用flag之前得先分清楚是local还是persistent
command.MarkFlagRequired("ip") // local
command.MarkPersistentFlagRequired("f") // persistent
a和b flag是绑定在一起的,指定a就必须指定b
startCmd.MarkFlagsRequiredTogether()
在声明Command的时候支持对args的校验
cobra.Command
的Args
属性
它提供了几个校验函数:
NoArgs
- the command will report an error if there are any positional args.ArbitraryArgs
- the command will accept any args.OnlyValidArgs
- the command will report an error if there are any positional args that are not in the ValidArgs
field of Command
.MinimumNArgs(int)
- the command will report an error if there are not at least N positional args.MaximumNArgs(int)
- the command will report an error if there are more than N positional args.ExactArgs(int)
- the command will report an error if there are not exactly N positional args.ExactValidArgs(int)
= the command will report and error if there are not exactly N positional args OR if there are any positional args that are not in the ValidArgs
field of Command
RangeArgs(min, max)
- the command will report an error if the number of args is not between the minimum and maximum number of expected args.不满足可以自定义
startCmd = &cobra.Command{
Use: "start", // 命令名字
Short: "start kla", // 短一点的描述
Long: `这就是一个用来测试的,start 命令可以启动kla,
一个尝尝的描述信息`, // 详细描述
Aliases: []string{"run"}, // 别名
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("启动kla,args:%v\n",args)
fmt.Printf("ip:%s\n",ip)
fmt.Printf("port:%d\n",port)
fmt.Printf("namespace:%s\n",namespace)
fmt.Printf("username:%s\n",username)
fmt.Printf("password:%s\n",password)
fmt.Printf("f:%s\n",configFilePath)
},
Args: func(cmd *cobra.Command, args []string) error {
// 他提供的函数式一个必包
nArgsFunc := cobra.MinimumNArgs(1)
// 先利用他的来做校验
err := nArgsFunc(cmd, args)
if err != nil {
return err
}
// 这是我自定义的校验
if args[0] != "testParam1" {
return fmt.Errorf("Illegal param")
}
// 没有问题就直接返回nil
return nil
},
}
支持在 -h(帮助输出)里面将命令分组
使用分组前,得先声明group,并将他添加到父命令中,并且在自己的command声明中,用GroupID
指定所在的group名
代码:
在root.go中声明group
startGroup = &cobra.Group{
ID: "startGroup",
Title: "开始命令",
}
stopGroup = &cobra.Group{
ID: "stopGroup",
Title: "结束命令",
}
添加到rootCmd中
rootCmd.AddGroup(startGroup,stopGroup)
在子命令中声明所在groupId
startCmd = &cobra.Command{
Use: "start", // 命令名字
Short: "start kla", // 短一点的描述
Long: `这就是一个用来测试的,start 命令可以启动kla,
一个尝尝的描述信息`, // 详细描述
Aliases: []string{"run"}, // 别名
GroupID: "startGroup",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("启动kla,args:%v\n",args)
fmt.Printf("ip:%s\n",ip)
fmt.Printf("port:%d\n",port)
fmt.Printf("namespace:%s\n",namespace)
fmt.Printf("username:%s\n",username)
fmt.Printf("password:%s\n",password)
fmt.Printf("f:%s\n",configFilePath)
},
重新编译,查看帮助信息即可
groupId是可以重复的,但是仅限于同一个父命令中
首先在start命令在增加一个子命令
/*
Copyright © 2022 NAME HERE
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// substartCmd represents the substart command
var substartCmd = &cobra.Command{
Use: "substart",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("substart called")
},
}
func init() {
startCmd.AddCommand(substartCmd)
}
将之前的的两个group添加到startCmd中,并且在subStartCmd中声明所属groupId
// 添加
startCmd.AddGroup(startGroup,stopGroup)
// 声明
var substartCmd = &cobra.Command{
Use: "substart",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("substart called")
},
GroupID: "startGroup",
}
重新编译,查看start的帮助信息
-> % ./kla start -h
这就是一个用来测试的,start 命令可以启动kla,
一个尝尝的描述信息
Usage:
kla start [flags]
kla start [command]
Aliases:
start, run
开始命令
substart A brief description of your command
结束命令
Flags:
-h, --help help for start
--ip string ip地址 (default "127.0.0.1")
-n, --namespace string namespace (default "default")
--password string password (default "admin")
--username string username (default "admin")
Global Flags:
--f string configPath
Use "kla start [command] --help" for more information about a command.
到这,这篇文章就结束了。