likes
comments
collection
share

Cobra 中文文档

作者站长头像
站长
· 阅读数 21

本人非专业翻译,也非专业 Go 开发,文档若有谬误,请勿对线,直接 编辑。原文档请 点击 查看。

关于

Cobra 是 Go 的 CLI 框架。它包含一个用于创建功能强大的现代 CLI 应用程序的库,以及一个用于快速生成基于 Cobra 的应用程序和命令文件的工具。

Cobra 由 Go 项目成员和 hugo 作者 spf13 创建,已经被许多流行的 Go 项目采用,比如 GitHub CLIDocker CLI

特性

  • 简单的基于子命令的 CLIs:app serverapp fetch 等;
  • 完全兼容 POSIX(可移植操作系统接口) 的标志(包括短版和长版)
  • 嵌套子命令
  • 全局、局部和级联的标志
  • 使用 cobra init appnamecobra add cmdname 轻松生成应用程序和命令
  • 智能提示(app srver ...did you mean app server
  • 自动生成命令和标志的帮助
  • 自动识别 -h--help 等帮助标识
  • 自动为你的应用程序生成的 bash 自动完成
  • 自动为你的应用程序生成 man 手册
  • 命令别名,以便你可以更改内容而不会破坏它们
  • 定义自己的帮助,用法等的灵活性。
  • 可选与 viper 紧密集成,可用于 12factor 应用程序

安装

Cobra 非常易用,首先使用 go get 命令安装最新版本。此命令将安装 cobra generator 的可执行文件及其依赖项:

$ go get -u github.com/spf13/cobra/cobra

概念

Cobra 构建在命令(commands)、参数(arguments)和 标志(flags)上。

Commands 代表动作,Args 是事物,Flags 是这些动作的修饰符。

最好的应用程序在使用时会像句子一样读起来。用户将知道如何使用该应用程序,因为他们将自然地了解如何使用它。

遵循的模式是 APPNAME VERB NOUN --ADJECTIVE。 或 APPNAME COMMAND ARG --FLAG

一些真实的例子可以更好地说明这一点。

在以下示例中,server 是命令,port 是标志:

hugo server --port=1313

在此命令中,我们告诉 Git 克隆 url 的内容:

git clone URL --bare

命令(Command)

命令是应用程序的核心。应用程序提供的每一个交互都包含在 Command 中。一个命令可以有子命令和可选的运行一个动作。

在上面的示例中,server 是命令。

cobra.Command API

标志(Flags)

一个标志是一种修饰命令行为的方式。Cobra 支持完全符合 POSIX(可移植操作系统接口) 的标志和 Go flag 包。

Cobra 命令可以定义一直保留到子命令的标志和仅可用于该命令的标志。

在上面的例子中,port 是标志。

标志的功能是 pflag 库提供的,该库是一个标准库的 fork,在维护相同接口的基础上兼容了 POSIX(可移植操作系统接口)

入门

欢迎大家提供自己的项目组织结构,但是通常基于 Cobra 的应用程序将遵循以下组织结构

  • cmd 放置命令的文件夹
    • add.go
    • your.go
    • commands.go
    • here.go
  • main.go 应用程序入口

在 Cobra 应用程序中,通常 main.go 文件非常。它有一个目的:初始化 Cobra。

package main

import (
  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

使用 Cobra 生成器

Cobra 提供了 CLI 来创建您的应用程序和添加任意你想要的命令。这是将 Cobra 集成到您的应用程序中的最简单方法。

这里 你可以查看更多关于生成器的资料。

使用 Cobra 库

要手动接入 Cobra,您需要创建一个 main.go 文件和 rootCmd 文件。您可以选择提供合适的其他命令。

创建 rootCmd

Cobra 不需要任何特殊的构造函数。只需创建您的命令。

理想情况下,将其放置在 /cmd/root.go 中:

// rootCmd 代表没有调用子命令时的基础命令
var rootCmd = &cobra.Command{
	Use:   "hugo",
	Short: "Hugo is a very fast static site generator",
  Long: `A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at http://hugo.spf13.com`,
  // 如果有相关的 action 要执行,请取消下面这行代码的注释
  // Run: func(cmd *cobra.Command, args []string) { },
}

// Execute 将所有子命令添加到root命令并适当设置标志。
// 这由 main.main() 调用。它只需要对 rootCmd 调用一次。
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

您还将在 init() 函数中定义标志并处理配置。例子如下:

// cmd/root.go
package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"

	homedir "github.com/mitchellh/go-homedir"
	"github.com/spf13/viper"
)

var cfgFile string
var projectBase string
var userLicense string

// rootCmd 代表没有调用子命令时的基础命令
var rootCmd = &cobra.Command{
	Use:   "hugo",
	Short: "Hugo is a very fast static site generator",
  Long: `A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at http://hugo.spf13.com`,
  // 如果有相关的 action 要执行,请取消下面这行代码的注释
  // Run: func(cmd *cobra.Command, args []string) { },
}

// Execute 将所有子命令添加到root命令并适当设置标志。会被 main.main() 调用一次。
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func init() {
	cobra.OnInitialize(initConfig)
	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
	rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/")
	rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution")
	rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)")
	rootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration")
	viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
	viper.BindPFlag("projectbase", rootCmd.PersistentFlags().Lookup("projectbase"))
	viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))
	viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
	viper.SetDefault("license", "apache")
}

func initConfig() {
	// Don't forget to read config either from cfgFile or from home directory!
	if cfgFile != "" {
		// Use config file from the flag.
		viper.SetConfigFile(cfgFile)
	} else {
		// Find home directory.
		home, err := homedir.Dir()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		// Search config in home directory with name ".cobra" (without extension).
		viper.AddConfigPath(home)
		viper.SetConfigName(".cobra")
	}

	if err := viper.ReadInConfig(); err != nil {
		fmt.Println("Can't read config:", err)
		os.Exit(1)
	}
}

创建 main.go

有了根命令,你需要一个 main 函数去执行它。为了清晰起见,Execute 应该在根目录上运行,尽管可以在任何命令上调用它。

在 Cobra 应用中,main.go 是非常简单的。它只有一个作用——初始化 Cobra。

// main.go
package main

import (
  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

创建额外的命令

可以定义其他命令,并且通常在 cmd/ 目录中为每个命令提供自己的文件。

如果要创建 version 命令,则可以创建 cmd/version.go 并使用以下命令进行填充:

// cmd/version.go
package cmd

import (
  "fmt"

  "github.com/spf13/cobra"
)

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

var versionCmd = &cobra.Command{
  Use:   "version",
  Short: "Print the version number of Hugo",
  Long:  `All software has versions. This is Hugo's`,
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
  },
}

使用标志

标志提供修饰符以控制命令的操作方式。

由于标志是在不同位置定义和使用的,我们需要在外部定义一个具有正确作用域的变量,以分配要使用的标志。

var verbose bool
var source string

这里有两种不同分配标志的方法。

持久标志

标志可以是 "persistent" 的,这意味着该标志将可用于分配给它的命令以及该命令下的每个命令。对于全局标志,将标志分配为根上的持久标志。

rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")

本地标志

也可以在本地分配一个标志,该标志仅适用于该特定命令。

rootCmd.Flags().StringVarP(&source, "source", "s", "", "Source directory to read from")

父命令上的本地标志

默认情况下,Cobra 仅解析目标命令上的本地标志,而忽略父命令上的任何本地标志。通过启用 Command.TraverseChildren,Cobra 将在执行目标命令之前解析每个命令上的本地标志

command := cobra.Command{
  Use: "print [OPTIONS] [COMMANDS]",
  TraverseChildren: true,
}

用配置绑定标志

您还可以将标志与 viper 绑定:

var author string

func init() {
  rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution")
  viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
}

在此示例中,持久标记 author 与 viper 绑定。请注意,当用户未提供 --author 标志时,变量 author 不会设置为 config 中的值。

更多信息请查看 viper

必需标志

标志默认是可选的。如果你想在缺少标志时命令报错,请设置该标志为必需:

var region string

rootCmd.Flags().StringVarP(&region, "region", "r", "", "AWS region (required)")
rootCmd.MarkFlagRequired("region")

位置和自定义参数

可以使用 Command 的 Args 字段指定位置参数的验证。

下面的验证符是内置的:

  • NoArgs - 如果有任何位置参数,该命令将报告错误。
  • ArbitraryArgs - 命令将接受任意参数
  • OnlyValidArgs - 如果 Command 的 ValidArgs 字段中不存在该位置参数,则该命令将报告错误。
  • MinimumNArgs(int) - 如果不存在至少 N 个位置参数,则该命令将报告错误。
  • MaximumNArgs(int) - 如果存在超过 N 个位置参数,则该命令将报告错误。
  • ExactArgs(int) - 如果不存在 N 个位置参数,则该命令将报告错误。
  • ExactValidArgs(int) - 如果没有确切的 N 个位置参数,或者如果 Command 的 ValidArgs 字段中不存在该位置参数,则该命令将报告并出错。
  • RangeArgs(min, max) - 如果 args 的数目不在期望的 args 的最小和最大数目之间,则该命令将报告并出错。

内置验证符使用实例:

var cmd = &cobra.Command{
	Use:   "hello",
	Short: "hello",
	Args:  cobra.MinimumNArgs(2),
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Hello, World!")
	},
}

如果只传递一个位置参数会报 Error: requires at least 2 arg(s), only received 1 的警告。

设置自定义验证器的示例:

var cmd = &cobra.Command{
  Short: "hello",
  Args: func(cmd *cobra.Command, args []string) error {
    if len(args) < 1 {
      return errors.New("requires at least one arg")
    }
    if myapp.IsValidColor(args[0]) {
      return nil
    }
    return fmt.Errorf("invalid color specified: %s", args[0])
  },
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("Hello, World!")
  },
}

示例

在下面的例子中,我们定义了三个命令。两个在顶层,一个(cmdTimes)是子命令。在这种情况下,根目录不可执行,这意味着需要一个子命令。通过不为 rootCmd 提供 Run 来实现。

我们只为一个命令定义了一个标志。

关于标志的文档在 [pflag]github.com/spf13/pflag…

package main

import (
	"fmt"
	"strings"

	"github.com/spf13/cobra"
)

func main() {
	var echoTimes int

	var cmdPrint = &cobra.Command{
		Use:   "Print [string to print]",
		Short: "Print anything to the screen",
		Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.`,
		Args: cobra.MinimumNArgs(1),
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Print: " + strings.Join(args, " "))
		},
	}

	var cmdEcho = &cobra.Command{
		Use:   "echo [string to echo]",
		Short: "Echo anything to the screen",
		Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.`,
		Args: cobra.MinimumNArgs(1),
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Print: " + strings.Join(args, " "))
		},
	}

	var cmdTimes = &cobra.Command{
		Use:   "times [# times] [string to echo]",
		Short: "Echo anyting to the screen more times",
		Long: `echo things multiple times back to the user y providing
		a count and a string.`,
		Args: cobra.MinimumNArgs(1),
		Run: func(cmd *cobra.Command, args []string) {
			for i := 0; i < echoTimes; i++ {
				fmt.Println("Echo: " + strings.Join(args, " "))
			}
		},
	}

	cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")

	// 设置根命令
	var rootCmd = &cobra.Command{Use: "app"}
	rootCmd.AddCommand(cmdPrint, cmdEcho)
	cmdEcho.AddCommand(cmdTimes)

	// 初始化应用
	rootCmd.Execute()
}

更复杂的应用,请参考 Hugo 或者 GitHub CLI

帮助命令

当你添加了子命令,Cobra 会自动添加一些帮助命令。当你执行 app help 命令时会显示帮助信息。另外,help 还支持其他命令作为输入参数。举例来说,你有一个没有额外配置的 create 命令,app help create 是有效的。每一个命令还会自动获取一个 --help 标志。

示例

以下输出由 Cobra 自动生成。 除了命令和标志定义外,什么都不需要。

Cobra  中文文档

help 就像其他命令一样。并没有特殊的逻辑或行为。实际上,你可以根据需要提供自己的服务。

定义你自己的 help

你可以使用下面的方法提供你自己的 Help 命令或模板。

cmd.SetHelpCommand(cmd *Command)
cmd.setHelpCommand(f func(*Command, []string))
cmd.setHelpTemplate(s string)

后两者也适用于所有子命令。

使用信息

当用户提供无效的标志或无效的命令时,Cobra 会通过向用户显示 usage 进行响应。

Cobra  中文文档

定义你自己的使用信息

你可以提供你自己的 usage 函数或模板。像 help 一样,函数和模板可通过公共方法重写:

cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)

可以参考 GitHub CLI 的写法。

版本标志

如果给根命令设置了 Version 字段,Cobra 会添加一个顶级的 --version 标志。运行带有 –version 标志的应用程序,将使用版本模板将版本打印到 stdout。模板可以使用 cmd.SetVersionTemplate(s string) 函数自定义。

SetVersionTemplate 的使用可以参考 GitHub CLI

PreRun 和 PostRun Hooks

可以在执行命令之前和之后运行一个函数。PersistentPreRunPreRun 函数将在 Run 之前执行。PersistentPostRunPostRun 会在 Run 之后运行。如果子级未声明自己的 Persistent * Run 函数,则子级将继承父级的。这些函数的执行顺续如下:

  • PersistentPreRun
  • PreRun
  • Run
  • PostRun
  • PersistentPostRun

下面这个包含了两个命令的例子使用了这些特性。当子命令执行时,它会运行根命令的 PersistentPreRun,但是不会运行根命令的 PersistentPostRun

package main

import (
	"fmt"

	"github.com/spf13/cobra"
)

func main() {
	var rootCmd = &cobra.Command{
		Use:   "root [sub]",
		Short: "My root command",
		PersistentPreRun: func(cmd *cobra.Command, args []string) {
			fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
		},
		PreRun: func(cmd *cobra.Command, args []string) {
			fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
		},
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Printf("Inside rootCmd Run with args: %v\n", args)
		},
		PostRun: func(cmd *cobra.Command, args []string) {
			fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
		},
		PersistentPostRun: func(cmd *cobra.Command, args []string) {
			fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
		},
	}

	subCmd := &cobra.Command{
		Use:   "sub [no options!]",
		Short: "My subcommand",
		PreRun: func(cmd *cobra.Command, args []string) {
			fmt.Printf("Inside subCmd PreRun with args: %v\n", args)
		},
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Printf("Inside subCmd Run with args: %v\n", args)
		},
		PostRun: func(cmd *cobra.Command, args []string) {
			fmt.Printf("Inside subCmd PostRun with args: %v\n", args)
		},
		PersistentPostRun: func(cmd *cobra.Command, args []string) {
			fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args)
		},
	}

	rootCmd.AddCommand(subCmd)

	rootCmd.SetArgs([]string{""})
	rootCmd.Execute()
	fmt.Println()
	rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
	rootCmd.Execute()
}

输出:

Inside rootCmd PersistentPreRun with args: []
Inside rootCmd PreRun with args: []
Inside rootCmd Run with args: []
Inside rootCmd PostRun with args: []
Inside rootCmd PersistentPostRun with args: []

Inside rootCmd PersistentPreRun with args: [arg1 arg2]
Inside subCmd PreRun with args: [arg1 arg2]
Inside subCmd Run with args: [arg1 arg2]
Inside subCmd PostRun with args: [arg1 arg2]
Inside subCmd PersistentPostRun with args: [arg1 arg2]

"unknown command" 时的提示

"unknown command" 错误发生时,Cobra 会自动打印提示。这和 git 命令的行为一致。比如

$ hugo srever
Error: unknown command "srever" for "hugo"

Did you mean this?
        server

Run 'hugo --help' for usage.

系统会根据注册的每个子命令自动生成建议,并使用萊文斯坦距離的实现。每个匹配最小距离 2(忽略大小写)的注册命令都将显示为建议。

如果需要禁用建议或在命令中调整字符串距离,请使用:

cmd.DisableSuggestions = true

cmd.SuggestionsMinimumDistance = 1

您还可以使用 SuggestFor 属性显式为给定命令设置建议的名称。这样就可以针对不是距离很近的字符串提出建议,但是对于您的命令集和不希望使用别名的命令来说,它们都是有意义的。比如:

$ kubectl remove
Error: unknown command "remove" for "kubectl"

Did you mean this?
        delete

Run 'kubectl help' for usage.

为你的命令生成文档

Cobra 可以基于子命令、标志等生成文档。可用格式如下:

为你的命令生成 Bash Completions

Cobra 可以生成 bash-completion 文件。如果你给你的命令添加了更多的信息,这些自动提示的分析会非常强大和灵活。更是信息请阅读Bash Completions

涂鸦智能大量优质 HC,欢迎大家加入,需要内推加我微信 yang_jun_ning,或直接发简历到邮箱 youngjuning@aliyun.com