如何使用Cobra开发自己的CLI

cobra logo

1:安装Cobra

首先我们要知道的就是Cobra是一个类似与框架的东西,也就是说它有一个cli来帮我们生成一个框架,那么我们就开始考虑这个Cobra怎么安装了,其实安装也非常的简单,我们来看一下如何安装

# 前提是有Go开发环境哦
go get -u github.com/spf13/cobra/cobra

执行上面命令安装完成之后它会在你的GOPATH/bin下下载一个二进制文件,windows叫做cobra-cli.exe

2:了解Cobra

首先在开始开发Cobra之前我们需要了解下,为什么我们要去开发CLI,或者说为什么我们需要CLI,那么这个时候我们就不得不提我们运维常用的一些CLI了,比如Docker,Kubernetes,Github-cli,Hugo,Helm,Istio,Etcd,Cilium等等这些都是使用Cobra开发的CLI,那么这些工具的作用是什么这个我想大家也都知道,所以这个就很简单的理解了,我们去开发CLI就是为了能够完成一些复杂的操作,可以让我用一个命令+一些参数完成这个操作,这就是我们开发CLI的目的。

3:基础Cobra使用

上面了解了Cobra的作用以及为什么我们要去使用CLI,那么下面我们来入门一下这个Cobra,首先我们创建一个项目

# 项目名称自己写,我这里写的是github地址因为到时候这个项目写完会丢到github上,其次在引用包的时候看起来更加的美观
go mod init github.com/gitlayzer/laycli

完事儿我们就开始使用cobra初始化一个CLI了,我们来看看如何初始化

# 可以使用-h查看帮助
cobra-cli init

# 它会帮你生成一个main.go和一个cmd的目录下面还有一个root.go的文件,当然main.go没什么可说的就是一个程序的入口,我们主要关注的就是cmd下的那个包。我们看看内容是什么
package cmd

import (
	"os"

	"github.com/spf13/cobra"
)



// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "laycli",
	Short: "A brief description of your application",
	Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. 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.`,
	// Uncomment the following line if your bare application
	// has an action associated with it:
	// Run: func(cmd *cobra.Command, args []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() {
	// Here you will define your flags and configuration settings.
	// Cobra supports persistent flags, which, if defined here,
	// will be global for your application.

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

	// Cobra also supports local flags, which will only run
	// when this action is called directly.
	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
如上图就是一个初始化后的项目,我们也可以称之为脚手架,第一个我们要看的就是`rootCmd`这个变量,它的类型是`&cobra.Command`,我们从代码也不难看出这个类型应该是个结构体,生成的代码里写了一个注释,意思是,当我们没有任何子命令的时候它就是一个基本的命令,里面的Use就是我们 CLI本身,再往下就是两个类似介绍的东西,再往下就是有一个操作如果我们有关联动作的话可能需要取消下面的注释,我们这里取消下
package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "laycli",
	Short: "A brief description of your application",
	Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. 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("This is Cobra CLI")
	},
}

// 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() {
	// Here you will define your flags and configuration settings.
	// Cobra supports persistent flags, which, if defined here,
	// will be global for your application.

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

	// Cobra also supports local flags, which will only run
	// when this action is called directly.
	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
目前就这么简单,然后我们就可以去编译一下我们的cli来看看效果

# 编译的平台取决于当前平台的系统和架构,因为Windows识别的是.exe结尾的程序,所以我生成的是带.exe结尾的,如果是Linux平台的就不需要了
PS E:\code\goland\laycli> go build -o laycli.exe

# 编译完成之后在我们的代码目录会出现一个二进制文件,我们就可以直接执行它
PS E:\code\goland\laycli> .\laycli.exe
This is Cobra CLI

# 这个时候我们看到我们刚刚写的东西居然生效了,其次我们还可以使用-h
PS E:\code\goland\laycli> .\laycli.exe -h
A longer description that spans multiple lines and likely contains
examples and usage of using your application. 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.

Usage:
  laycli [flags]

Flags:
  -h, --help     help for laycli
  -t, --toggle   Help message for toggle

# 可以看到这里就是我们的介绍和cli帮助了

其次我们往下接着分析,可以看到一个init函数,这个时候我相信大家应该对Go了解的都知道,一个包内如果有init的函数那么这个包被调用的时候它第一个执行的函数就是init函数,其实在老版本的Cobra的时候它的配置文件引入的操作还是没有被注释的,但是新版的就默认不开启配置文件了,当然如果有需要自己配置一下注释去掉就OK了,那么我们也就去掉,顺便声明一个cfgFile的变量
package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var cfgFile string

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "laycli",
	Short: "A brief description of your application",
	Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. 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("This is Cobra CLI")
	},
}

// 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() {
	// Here you will define your flags and configuration settings.
	// Cobra supports persistent flags, which, if defined here,
	// will be global for your application.

	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.laycli.yaml)")
	//
	// Cobra also supports local flags, which will only run
	// when this action is called directly.
	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
通过如上我们可以看出它有两种标识符`PersistentFlag`和`LocalFlag`

1:PersistentFlag:它可以用于rootCmd也可以用于rootCmd的子命令当中去
2:LocalFlag:它只可以用于rootCmd,而不能用于子命令

PS E:\code\goland\laycli> .\laycli.exe -t          
This is Cobra CLI

PS E:\code\goland\laycli> .\laycli.exe --config layzer
This is Cobra CLI


# 这里其实我们没有实现什么,所以看不出来效果,但是它并没有给我们报错,证明它运行了,往下其实原来的老版本还有一个initConfig的操作,也就是支持配置文件的一个引入,但是这个好像在新版本已经自动引入了,不过我们暂时不考虑这个,我们先看我们如何写一个自己的命令,比如我们要实现一个

laycli add 1 2 3 然后它自动给我们一个相加的结果

# 这里需要说明一点,cobra add 内的这个add是属于cobra这个cli,后面才是我们程序的add
PS E:\code\goland\laycli> cobra-cli.exe add add
add created at E:\code\goland\laycli

# 然后它会在cmd下生成一个以命令命名的go文件,我们来看看内容
package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

// addCmd represents the add command
var addCmd = &cobra.Command{
	Use:   "add",
	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("add called")
	},
}

func init() {
	rootCmd.AddCommand(addCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// addCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
可以看出它好像和root.go非常的像,只是少了一部分的代码,从代码里可以看出其实`init`内引入了一个rootCmd并注册了`addCmd`这个变量,而这个变量就是我们操作的一个东西,当我们注册好了就可以使用 laycli add 了,但是要实现上面说的那个操作,还是需要针对这个add的包进行一个开发的,那么我们来看看如何开发它

# 我们先看看add是否成为了我们cli的子命令
PS E:\code\goland\laycli> .\laycli.exe add      
add called

# 然后我们开发一下我们的需求,首先我们要实现的就是如何拿到我们的传参,下面看代码
package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

// addCmd represents the add command
var addCmd = &cobra.Command{
	Use:   "add",
	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.Printf("args: %v", args)
	},
}

func init() {
	rootCmd.AddCommand(addCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// addCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
# 可以看到没什么变化,我只是改了打印,这个时候我们去编译并打印一下

PS E:\code\goland\laycli> .\laycli.exe add 1 2 3 
args: [1 2 3]

# 这里可以看到,参数其实已经到我们这儿了,我们要处理的就是把args处理成我们想要的东西然后再进行一系列操作,我们看看我们如何处理它
package cmd

import (
	"fmt"
	"github.com/spf13/cobra"
	"strconv"
)

// addCmd represents the add command
var addCmd = &cobra.Command{
	Use:   "add",
	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) {
		toIntAdd(args)
	},
}

func init() {
	rootCmd.AddCommand(addCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// addCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func toIntAdd(args []string) {
	var sum int

	for _, v := range args {
		temp, err := strconv.Atoi(v)
		if err != nil {
			fmt.Printf("Error:%s", err)
			return
		} else {
			sum += temp
		}
	}

	fmt.Printf("Add of numbers %s is %d", args, sum)
}

# 那么这个时候其实我们就开发好了,我们再次构建再次执行一下,看看是什么结果

PS E:\code\goland\laycli> .\laycli.exe add 1 2 3                   
Add of numbers [1 2 3] is 6

# 可以看到这里是按照我们的开发出来逻辑去做的,就是打印粗参数和最后的操作结果,完全符合我们的要求,但是其实这里面还是有Bug的,因为它目前传值只能是int类型,因为我们的处理函数只用了string转int,所以只能传数字,但是但凡是一个其他参数就会爆错了,我们来看看

PS E:\code\goland\laycli> .\laycli.exe add 1 2 3 4 5.5
Error:strconv.Atoi: parsing "5.5": invalid syntax

# 可以看到,报错被丢出来了,虽然有结果,但是打眼一看结果肯定不对啊,但是按理还说既然都错了,怎么还能算出结果,这个其实因为我们再toIntAdd的报错内没有返回,可以报错,也可以直接return

# 那么这个时候我们其实可以用一个参数来说明是要计算int还是其他的,那么我们看看怎么做
package cmd

import (
	"fmt"
	"github.com/spf13/cobra"
	"strconv"
)

// addCmd represents the add command
var addCmd = &cobra.Command{
	Use:   "add",
	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) {
		status, _ := cmd.Flags().GetBool("float")
		if status {
			toFloatAdd(args)
		} else {
			toIntAdd(args)
		}
	},
}

func init() {
	rootCmd.AddCommand(addCmd)

	addCmd.Flags().BoolP("float", "f", false, "Add float numbers")
	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// addCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func toIntAdd(args []string) {
	var sum int

	for _, v := range args {
		temp, err := strconv.Atoi(v)
		if err != nil {
			fmt.Printf("\033[31mError:%s\033[0m", err)
			return
		} else {
			sum += temp
		}
	}

	fmt.Printf("Add int of numbers %s is %d", args, sum)
}

func toFloatAdd(args []string) {
	var sum float64

	for _, v := range args {
		temp, err := strconv.ParseFloat(v, 64)
		if err != nil {
			fmt.Printf("\033[31mError:%s\033[0m", err)
			return
		} else {
			sum += temp
		}
	}

	fmt.Printf("Add float of numbers %s is %f", args, sum)
}
# 这里其实是又写了一个方法来处理float,然后在实际调用的时候判断了一下是否传递了 --float这个参数,如果没有就执行`toIntAdd`,如果传递了,则执行`toFloatAdd`,那么,我们来验证一下

PS E:\code\goland\laycli> .\laycli.exe add 1 2 3 4 5.5 -f     
Add float of numbers [1 2 3 4 5.5] is 15.500000


可以看到,算出结果了,而且结果是一样的,这样我们其实就实现了兼容`Float`的一个计算

然后我们还是要强调一下,全局参数和局部参数的区别,

# 只支持主命令的参数
PS E:\code\goland\laycli> .\laycli.exe add -t                     
Error: unknown shorthand flag: 't' in -t
Usage:
  laycli add [flags]

Flags:
  -f, --float   Add float numbers
  -h, --help    help for add

Global Flags:
      --config string   config file (default is $HOME/.laycli.yaml)
      
# 不管是子命令还是主命令都支持的参数
PS E:\code\goland\laycli> .\laycli.exe add --config=~/.laycli.yaml
Add int of numbers [] is 0


# 然后我们是否可以给add再添加子命令?这个答案显而易见是可以的。我们再来一个需求,就是只希望得到偶数相加的结果,那么我们动手实现一下吧

PS E:\code\goland\laycli> cobra-cli.exe add even
even created at E:\code\goland\laycli

# 然后我们我们需要将其作为add的子命令,所以我们需要稍作修改
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

// evenCmd represents the even command
var evenCmd = &cobra.Command{
	Use:   "even",
	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("even called")
	},
}

func init() {
	addCmd.AddCommand(evenCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// evenCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// evenCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
# 我们直接用addCmd去注册它就好了,然后我们编译测试一下

PS E:\code\goland\laycli> .\laycli.exe add -h
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.

Usage:
  laycli add [flags]
  laycli add [command]

Available Commands:
  even        A brief description of your command

Flags:
  -f, --float   Add float numbers
  -h, --help    help for add

Global Flags:
      --config string   config file (default is $HOME/.laycli.yaml)

Use "laycli add [command] --help" for more information about a command.


# 从这里可以看到even来了,也就是成为了add的子命令,并且主命令也看不到

PS E:\code\goland\laycli> .\laycli.exe -h    
A longer description that spans multiple lines and likely contains
examples and usage of using your application. 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.

Usage:
  laycli [flags]
  laycli [command]

Available Commands:
  add         A brief description of your command
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command

Flags:
      --config string   config file (default is $HOME/.laycli.yaml)
  -h, --help            help for laycli
  -t, --toggle          Help message for toggle

Use "laycli [command] --help" for more information about a command.

# 那么我们开始处理
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
	"fmt"
	"strconv"

	"github.com/spf13/cobra"
)

// evenCmd represents the even command
var evenCmd = &cobra.Command{
	Use:   "even",
	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) {
		toEvent(args)
	},
}

func init() {
	addCmd.AddCommand(evenCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// evenCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// evenCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func toEvent(args []string) {
	var evenSum int

	for _, v := range args {
		temp, err := strconv.Atoi(v)
		if err != nil {
			fmt.Printf("\033[31mError:%s\033[0m", err)
			return
		} else if temp%2 == 0 {
			evenSum += temp
		}
	}

	fmt.Printf("The even add of %v is %d", args, evenSum)
}
# 写完之后我们测试一下

PS E:\code\goland\laycli> .\laycli.exe add even 1 2 3 4 5 6 
The even add of [1 2 3 4 5 6] is 12

# 可以看到它只算了复数,也就是2+4+6,结果是12,这就是我们需要的答案,其实我们上面都是在教大家如何创建一个自己的命令包括给命令加子命令,顺便我们也把基数的计算也写出来

PS E:\code\goland\laycli> cobra-cli.exe add odd
odd created at E:\code\goland\laycli

# 编写逻辑
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
	"fmt"
	"github.com/spf13/cobra"
	"strconv"
)

// oddCmd represents the odd command
var oddCmd = &cobra.Command{
	Use:   "odd",
	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) {
		toOdd(args)
	},
}

func init() {
	addCmd.AddCommand(oddCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// oddCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// oddCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func toOdd(args []string) {
	var oddSum int
	for _, v := range args {
		temp, err := strconv.Atoi(v)
		if err != nil {
			fmt.Printf("\033[31mError:%s\033[0m", err)
			return
		} else if temp%2 != 0 {
			oddSum += temp
		}
	}

	fmt.Printf("The odd add of %v is %d", args, oddSum)
}
# 写完我们验证一下结果

PS E:\code\goland\laycli> .\laycli.exe add odd 1 2 3 4 5 6 
The odd add of [1 2 3 4 5 6] is 9

# 可以看到这就是基数的计算,其实这就是Cobra的最基本的使用方法,主要还是看我们如何处理传递进来的参数,如何去针对参数进行传递和结合处理。
posted @ 2023-03-23 16:05  Layzer  阅读(141)  评论(0编辑  收藏  举报