Cobra 简介

Cobra 是一个流行的 Go 语言库,用于创建强大且灵活的命令行应用程序。它由 spf13 开发,设计用于与 Go 生态系统中的其他流行库(如 Viper 配置库)无缝集成。Cobra 支持多级命令结构,允许定义根命令和任意数量的子命令,还可以轻松处理全局和本地标志。它自动生成帮助和使用信息,并支持 BashZshFishPowerShell 的命令补全。此外,Cobra 能够生成 Markdown 格式的文档,使文档维护更加便捷。通过与 Viper 集成,Cobra 能处理配置文件和环境变量,为开发者提供了强大的工具集,使创建复杂的 Client 工具变得简单高效。Cobra 广泛应用于各种 Go 项目中,提升了 Client 应用的开发体验和维护效率。

下面是 Cobra 主要的功能:

  • 支持多级命令结构:轻松定义根命令和任意数量的子命令,组织复杂的命令行应用。
  • 处理全局和本地标志:解析和处理命令行参数,支持全局标志和局部标志。
  • 自动生成帮助和使用信息:根据命令和标志自动生成详细的帮助和使用信息。
  • 支持命令补全:提供 Bash、Zsh、Fish 和 PowerShell 的命令补全脚本,提升用户体验。
  • 生成 Markdown 格式的文档:自动生成命令行工具的 Markdown 格式文档,方便文档维护。

Cobra 的使用非常广泛,特别是在云原生和 DevOps 工具中。下面是使用 Cobra 构建 CLI 工具的知名工具:

  • Kubernetes (k8s):这个流行的容器编排平台的命令行工具 kubectl 就是使用 Cobra 构建的。
  • Docker:Docker CLI 也是基于 Cobra 开发的。
  • Hugo:这个流行的静态网站生成器使用 Cobra 来构建其命令行界面。
  • GitHub CLI:GitHub 的官方命令行工具 gh 也使用了 Cobra。
  • CoreDNS:这个灵活可扩展的 DNS 服务器使用 Cobra 来管理其命令行接口。

下面让我们一起进入正题,Cobra 上手教程。

Cobra 上手教程

第一步我们要创建一个项目,然后为项目安装 Cobra

go get -u github.com/spf13/cobra@latest

我们在 main.go 中添加如下代码来创建第一个命令行工具:

package main  
  
import (  
    "fmt"  
    "github.com/spf13/cobra"    "log")  
  
func main() {  
    funCmd := &cobra.Command{  
       Use:   "fun",  
       Short: "Demo of Cobra,打印 FunTester !!!",  
       Long:  "Demo of Cobra,打印 FunTester !!!,使用Cobra构建命令行工具,实现简单的命令行功能",  
       Run: func(cmd *cobra.Command, args []string) {  
          fmt.Println("Hello, FunTester!")  
       },  
    }  
  
    if err := funCmd.Execute(); err != nil {  
       log.Fatal(err)  
    }  
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

这是参数的解释:

  • Use: 命令的使用方法,通常是命令的名称。
  • Short: 简短描述,在帮助信息中显示。
  • Long: 详细描述,在帮助信息中显示。
  • Run: 命令执行时的函数。

我们本地编译执行 ./funtester ,控制台就会打印

Hello, FunTester!
  • 1.

接收参数

Cobra 中,处理命令行参数是构建命令行应用的核心部分。Cobra 提供了多种方式来接收和处理参数,包括标志(flags)和位置参数(arguments)。

flags 参数

标志是用于提供额外信息的参数,通常以 --flag=value 或 -f value 形式出现。Cobra 支持全局标志和本地标志,标志可以是布尔型、整数型、浮点型或字符串型。

我们可以在原来的程序中,创建 funCmd 之后添加这么一段代码:

// 添加命令行参数,并设置默认值,使用StringVarP方法,第一个参数是指针,第二个参数是命令行参数名称,第三个参数是命令行参数的简写,第四个参数是默认值,第五个参数是命令行参数的描述  
funCmd.Flags().StringP("name", "n", "FunTester", "姓名,用于打印")
  • 1.
  • 2.

然后我们在参数 Run 参数中这么处理:

Run: func(cmd *cobra.Command, args []string) {  
    name, err := cmd.Flags().GetString("name")  
    if err == nil {  
       fmt.Printf("Hello, %s ! \n", name)  
    } else {  
       fmt.Println(fmt.Sprintln(err))  
    }  
},
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

这样我们就可以接收参数了,使用方法如下:

╰─⠠⠵ ./funtester --name FunTester001
Hello, FunTester001 ! 
╰─⠠⠵ ./funtester -n FunTester002
Hello, FunTester002 ! 
  • 1.
  • 2.
  • 3.
  • 4.

同样地,Cobra 还提供了整型和浮点型数据接收和获取方法,这里就不一一展示了。完整代码如下:

package main  
  
import (  
    "fmt"  
    "github.com/spf13/cobra"    "log")  
  
func main() {  
    funCmd := &cobra.Command{  
       Use:   "fun",  
       Short: "Demo of Cobra,打印 FunTester !!!",  
       Long:  "Demo of Cobra,打印 FunTester !!!,使用Cobra构建命令行工具,实现简单的命令行功能",  
       Run: func(cmd *cobra.Command, args []string) {  
          name, err := cmd.Flags().GetString("name")  
          if err == nil {  
             fmt.Printf("Hello, %s age: %s ! \n", name, age)  
          } else {  
             fmt.Println(fmt.Sprintln(err))  
          }  
       },  
    }  
    // 添加命令行参数,并设置默认值,使用StringVarP方法,第一个参数是指针,第二个参数是命令行参数名称,第三个参数是命令行参数的简写,第四个参数是默认值,第五个参数是命令行参数的描述  
    funCmd.Flags().StringP("name", "n", "FunTester", "姓名,用于打印")  
    if err := funCmd.Execute(); err != nil {  
       log.Fatal(err)  
    }  
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

如果我们想直接使用当前的属性或者对象接收参数值,我们可以用下面的方法:

package main  
  
import (  
    "fmt"  
    "github.com/spf13/cobra"    "log")  
  
func main() {  
    var name string  
    funCmd := &cobra.Command{  
       Use:   "fun",  
       Short: "Demo of Cobra,打印 FunTester !!!",  
       Long:  "Demo of Cobra,打印 FunTester !!!,使用Cobra构建命令行工具,实现简单的命令行功能",  
       Args:  cobra.ExactArgs(1), // 限制参数个数,只能有一个参数,否则报错  
       Run: func(cmd *cobra.Command, args []string) {  
          fmt.Printf("Hello, %s ! \n", name)  
       },  
    }  
    // 添加命令行参数,并设置默认值,和提示信息,以及参数的简写,和参数的使用说明  
    funCmd.Flags().StringVarP(&name, "name", "n", "FunTester", "姓名,用于打印")  
    if err := funCmd.Execute(); err != nil {  
       log.Fatal(err)  
    }  
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

如果我们定义一些必传参数,可以用下面的语法:

funCmd.MarkFlagRequired("name")

位置参数

位置参数是命令行中紧跟在命令后面的参数,它们不以 --flag 的形式出现。位置参数可以是必需的,也可以是可选的。

我们在上一版代码的基础上添加新的代码,增加了 Args 参数控制位置参数的个数,然后在 Run 参数中处理位置参数。完整代码如下:

package main  
  
import (  
    "fmt"  
    "github.com/spf13/cobra"    "log")  
  
func main() {  
    funCmd := &cobra.Command{  
       Use:   "fun",  
       Short: "Demo of Cobra,打印 FunTester !!!",  
       Long:  "Demo of Cobra,打印 FunTester !!!,使用Cobra构建命令行工具,实现简单的命令行功能",  
       Args:  cobra.ExactArgs(1), // 限制参数个数,只能有一个参数,否则报错  
       Run: func(cmd *cobra.Command, args []string) {  
          age := args[0]  
          name, err := cmd.Flags().GetString("name")  
          if err == nil {  
             fmt.Printf("Hello, %s age: %s ! \n", name, age)  
          } else {  
             fmt.Println(fmt.Sprintln(err))  
          }  
       },  
    }  
    // 添加命令行参数,并设置默认值,使用StringVarP方法,第一个参数是指针,第二个参数是命令行参数名称,第三个参数是命令行参数的简写,第四个参数是默认值,第五个参数是命令行参数的描述  
    funCmd.Flags().StringP("name", "n", "FunTester", "姓名,用于打印")  
    if err := funCmd.Execute(); err != nil {  
       log.Fatal(err)  
    }  
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

下面我们来编译执行看看。

╰─⠠⠵ ./funtester 123 --name FunTester002
Hello, FunTester002 age: 123 ! 
╰─⠠⠵ ./funtester  --name FunTester002 3242
Hello, FunTester002 age: 3242 ! 
╰─⠠⠵ ./funtester --name FunTester002    
Error: accepts 1 arg(s), received 0
Usage:
  fun [flags]

Flags:
  -h, --help          help for fun
  -n, --name string   姓名,用于打印 (default "FunTester")

2024/07/26 15:21:23 accepts 1 arg(s), received 0

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

可以看出,位置参数无论在前还是在后,都不影响,但是如果缺少,就会报错。不得不说,Cobra 兼容性还不错。

如果我们位置参数数量不固定,对于参数的要求也比较多,可以参考 cobra.PositionalArgs 来解决,这里定义:

type PositionalArgs func(cmd *Command, args []string) error

下面是 Cobra 提供的几种实现类型:

在 Cobra 中,PositionalArgs 是一个用于定义和验证命令行位置参数的类型。它允许你指定命令的参数数量和验证规则。以下是几种常见的 PositionalArgs 验证函数以及如何使用它们的示例:

常见的 PositionalArgs 验证函数
  1. cobra.NoArgs:不接受任何位置参数。
  2. cobra.ArbitraryArgs:接受任意数量的位置参数。
  3. cobra.ExactArgs(n int):接受确切数量的参数。
  4. cobra.MinimumNArgs(n int):接受至少 n 个参数。
  5. cobra.MaximumNArgs(n int):接受最多 n 个参数.
  6. cobra.RangeArgs(min, max int):接受参数数量在指定范围内(包括边界)。
示例代码

以下是一些使用 PositionalArgs 验证函数的示例:

  • cobra.NoArgs: 不接受任何位置参数。
  • cobra.ArbitraryArgs: 接受任意数量的位置参数。
  • cobra.ExactArgs(n int): 接受确切数量的参数。
  • cobra.MinimumNArgs(n int): 接受至少 n 个参数。
  • cobra.MaximumNArgs(n int): 接受最多 n 个参数。
  • cobra.RangeArgs(min, max int): 接受参数数量在指定范围内。

这些验证函数使得处理和验证命令行参数变得更加简单和直观。根据应用的需求选择合适的 PositionalArgs 函数,可以确保用户提供正确数量和类型的参数,从而提高应用的可靠性和用户体验。

全局共享标志

Cobra 中,PersistentFlags 是用于在所有命令(包括子命令)中共享的标志。PersistentFlags 通常用于定义全局设置或配置参数,这些参数在所有命令中都可以使用。

使用方法如下:

// 为命令添加参数,默认值为FunTester  
funCmd.PersistentFlags().StringP("name", "n", "FunTester", "姓名,用于打印")
  • 1.
  • 2.

这个方法通常在构建子命令的时候用到,在父命令添加标志,所有的子命令也同时会拥有同样的标识。

子命令

在 Cobra 中,子命令(Subcommands)是命令行工具的一部分,使得工具能够包含多个操作或功能,每个子命令都可以有自己的标志和参数。子命令让命令行工具更加模块化和灵活。

下面是一个简单的例子:

package main  
  
import (  
    "fmt"  
    "github.com/spf13/cobra"    "log")  
  
func main() {  
    var name string  
    funCmd := &cobra.Command{  
       Use:   "fun",  
       Short: "Demo of Cobra,打印 FunTester !!!",  
       Long:  "Demo of Cobra,打印 FunTester !!!,使用Cobra构建命令行工具,实现简单的命令行功能",  
       Run: func(cmd *cobra.Command, args []string) {  
          fmt.Printf("Hello, %s ! \n", name)  
       },  
    }  
    // 为命令添加参数,默认值为FunTester  
    funCmd.PersistentFlags().StringVarP(&name, "name", "n", "FunTester", "姓名,用于打印")  
    funCmd.MarkFlagRequired("name") // 标记为必须参数  
    sub := &cobra.Command{  
       Use:   "sub",  
       Short: "sub command",  
       Run: func(cmd *cobra.Command, args []string) {  
          fmt.Println("sub command print: " + name)  
       },  
    }  
    funCmd.AddCommand(sub)  
    if err := funCmd.Execute(); err != nil {  
       log.Fatal(err)  
    }  
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

使用方法如下:

╰─⠠⠵ ./funtester sub --name FunTester002 32432
sub command print: FunTester002
  • 1.
  • 2.

其他

Cobra 其他一些高级功能,暂时还用不到,有兴趣的可以自行研究研究。PS:要生成 Markdown 文档并将其转换为 man 页面格式,可以使用 github.com/cpuguy83/go-md2man/v2/md2man 包。也可以使用 --help 来获取自动生成的提示信息。