1、cobra使用

包含两个模块:
(1)先用Generator自动生成模板,
(2)再依据Cobra Library修改模板,设置自己的参数。

1、Generator使用

注意:由于代码是自动生成的,最好不要在自己项目中随便用这生成命令,因为一不小心就把你的main.go全部覆盖了!!!
建议:建个小demo,配置好了再粘到你的项目中的cli相关目录下就行。

1.1 命令生成cobra代码

(1)新建Go项目并初始化
手动创建你的项目文件夹cobra_test1
go mod init cobra_test1 //go项目的初始化,生成go.mod

坑1:go mod init github.com/spf13/cobra_test1
【不要用这个命令!】

这个命令的含义是,项目名称是"github.com/spf13/cobra_test1",而不是"cobra_test1",而默认的main.go中引用的是后者,因此找不到,飙红!

一开始我就这么用的,坑死了,这玩意会在cobra_test1的文件夹外面生成.sum文件,导致引入路径错误!
当时问题是"cmd"引入飙红,只发现了莫名其妙在文件夹外出现的.sum文件,但不知道哪步错了,死活找不到原因。
只好全删了重新按照普通go项目的init来。
当时录了屏,第二天看录屏的时候才发现,比坑2还要隐蔽!

(2)安装cobra generator
go install github.com/spf13/cobra-cli@latest //安装之后cobra-cli命令才能使用
(3)执行cobra的初始化
cobra-cli init // 此命令自动生成的文件有:go.sum,LICENSE,main.go,cmd/root.go
自动生成的目录结构如下:

坑2:cobra-cli init cobra_test1
【不要用这个命令!】
不要在后面跟cobra_test1,否则其生成的目录结构如下:

此时main.go中import cmd 时会飙红,因为cmd的路径变成了嵌套的,
而默认是不嵌套的"cobra_test1/cmd",需要改成"cobra_test1/cobra_test1/cmd"

(4)添加command
假如你想在项目中执行以下命令:

cobra_test1 serve         // serve和config都是root的子命令
cobra_test1 config
cobra_test1 config create // create是config的子命令
cobra_test1 config delete // delete是config的子命令

那么你应该在cobra_test1根目录下,继续执行以下命令:

cobra-cli add serve
cobra-cli add config
cobra-cli add create -p 'configCmd' //注意,这里是配置"config"的子命令,因此要用`-p 'configCmd'`
cobra-cli add delete -p 'configCmd' //同上

执行后会在cmd下自动生成serve.go config.go create.go delete.go四个文件,
目录如下:

1.2 生成的cobra代码含义

生成的文件有:go.sum,LICENSE,main.go,cmd/root.go cmd/serve.go cmd/config.go cmd/create.go cmd/delete.go
go.sum里面是cobra的依赖包,不用管;LICENSE是空的,暂时不用管;
main.go调用cmd.Execute()函数:

package main
import "cobra_test1/cmd"
func main() {
	cmd.Execute()
}

cmd/root.go包含三部分:
(1)变量rootCmd定义,表示base command
(2)init()函数,定义flags和configuration settings/handle configuration
(3)Execute() 函数,调用rootCmd.Execute()

package cmd
import (
	"os"
	"github.com/spf13/cobra"
)

// rootCmd表示base command,调用时没有任何subcommands
var rootCmd = &cobra.Command{
	Use:   "cobra_test1",
	Short: "A brief description of your application",
	Long: `……`,
}

// Execute添加所有child commands到root command,并且适当地设置flags.
// Execute在main.main()中调用,仅需要在rootCmd上执行一次. 
func Execute() {
	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}

//定义flags和configuration settings
func init() { 
	// persistent flags, which, if defined here, will be global for your application.
	// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra_test1.yaml)")

	// local flags, which will only run when this action is called directly.
	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

cmd/serve.go包含两部分:
(1)变量serveCmd定义,表示base command
(2)init()函数,定义flags和configuration settings/handle configuration

package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

var serveCmd = &cobra.Command{
	Use:   "serve",
	Short: "简述你的command",
	Long: `更长的描述,可跨多行,可以包含你command的案例和使用,例如:

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("serve called")
	},
}

func init() {
	rootCmd.AddCommand(serveCmd)
}

1.3 运行代码

go run main.go serve // 执行serveCmd.Run:func()的内容,输出:serve called
go run main.go config // 输出:config called

注意,由于只是一个demo项目,main函数只有一个execute函数的调用,然后就结束了,因此每次执行一条命令就结束了。
指令运行是一次性的。
故,每次都需要在前面带上"go run main.go"

如果想要main不结束,需要借助多协程,
把这个cli加入自己项目的方法,就是在相应指令的Run:func()中执行相应的函数,
而这些函数一般都是多协程的,程序就不会直接结束了。

参考:
https://www.freesion.com/article/80051415478/

2、使用Cobra Library设置参数

参数两种配置方式:
(1)用command+args方式,不需要flag名称
需要考虑args的过滤!

我的理解:args是那种没有默认值的,可填可不填的参数。而如果要用默认值,最好用flag

git clone "xxx"  后面跟的就是一个arg,实际上是地址。
conn "127.0.0.1:8888"      和服务器建立ws连接,地址格式"127.0.0.1:8888"

(2)用--flag value方式,需要flag名称,即全局/局部的flags。


//这个还没设计好,按理说应该把cyclic和period作为args,而最后的--table作为flag比较好吧??但是多个值又怎么处理呢??

//目前下面这种是三个flag的,过于复杂了。

send [--cyclic] [--period] --table    向服务器发送1203表的数据【send -c -p=100 1203/1204/1205, 1206 1207 】

	-t, --table int          向服务器发送的数据表号,必须是整型 (默认 --table=1203 )【多个用“/”隔开怎么设置?】
	-c, --cyclic bool        区分周期/非周期数据表,true为周期,false为非周期(默认 -c=true/1或 -c)【如果没有这个参数表示非周期,可是这怎么设置?】
	-p, --period int         周期数据的发送周期,单位是ms

参数分类、含义:2种,全局persistent flags和局部local flags
在哪里:在对应命令的init()函数中
那些参数可配置、如何配置:

rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra_test1.yaml)")

//函数定义如下:
func (f *FlagSet) StringVar(p *string, name string, value string, usage string)
StringVar定义一个string flag,共四个参数:
  name:flag的名称
  value:flag的默认值
  usage:flag的用法描述
  p:指针,指向一个string变量,这个变量存储flag的值

问题:
(1)p这个指针对应的变量在哪里定义?


rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")

参考:
https://cobra.dev/
https://github.com/spf13/cobra-cli/blob/main/README.md 【坑1 2】
https://github.com/spf13/cobra/blob/main/user_guide.md
https://www.cnblogs.com/niuben/p/13886555.html 【英文教程太难搞了,绕弯弯也不说参数到底是什么语法,对应命令的案例。还是这个中文教程清晰明了】
https://www.thorsten-hans.com/lets-build-a-cli-in-go-with-cobra/ 【带命令使用的案例】
https://umarcor.github.io/cobra/ 【这个教程也很好,有命令使用案例!而且左右分开很清晰】
https://github.com/divrhino/studybuddy 【有视频+代码,但是仍然不知道怎么交互的】

2、总结

(1)有上下文可交互的工具grumble,有空试试

参考:
https://zhuanlan.zhihu.com/p/298639762

(2)spf13/cobra 和 urfave/cli(提到的所有cli工具),都是一次性的程序,没有上下文。也就是说,其交互方式,不能连续输入指令,每输入一次指令,相当于重启一次程序。

如果需要记录上下文,需要手动添加os.Stdin,读入参数后按空格拆分,手动判断到底是哪个命令,从而执行该函数。这样的话,连输入和分支的逻辑都需要自己写,他这个cli的意义就不大了,仅仅是辅助设计命令,不要也罢。

关于c-bata/go-prompt:

现在唯一流行的是 c-bata/go-prompt 这个库,它做到了类 python prompt toolkits 的体验,也就是能够很美观地自动补全。
一个命令行工具基于 go-prompt 仍要再堆不少代码,才可在真实世界使用。所以 go-prompt 可谓一个专精的库,而非完整的框架。

——————
更多cobra:
https://juejin.cn/post/6975039037673472013
https://www.jianshu.com/p/99aae91587af
https://piaohua.github.io/post/golang/20220807-cobra-cli/
https://www.jianshu.com/p/790dc1171bbf
https://juejin.cn/post/6924541628031959047

posted on 2023-04-12 17:17  西伯尔  阅读(118)  评论(0编辑  收藏  举报