Cobra

一个用于现代 CLI 应用的 Go 框架 Go

关于

logo.png

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

它是由 Go 团队成员 spf13hugo 创建的,并且已被最流行的 Go 项目采用。

  • 易于使用的基于子命令的 CLI:app serverapp fetch 等。
  • 完全符合 POSIX 标准的标志(包括短标志和长标志)
  • 嵌套子命令
  • 全局、本地和级联标志
  • 使用 cobra init appnamecobra add cmdname 轻松生成应用程序和命令
  • 智能建议(app srver… 您是否想输入 app server?)
  • 自动生成命令和标志的帮助信息
  • 自动识别 -h--help 等帮助标志
  • 自动为您的应用程序生成 bash 自动补全
  • 自动为您的应用程序生成手册页
  • 命令别名,以便您可以在不破坏它们的情况下更改内容
  • 灵活地定义您自己的帮助信息、用法等
  • 可选地与 viper 紧密集成,用于 12 因子应用程序

安装

使用 Cobra 很容易。首先,使用 go get 安装最新版本的库。此命令将安装 cobra 生成器可执行文件以及库及其依赖项

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

接下来,将 Cobra 包含在您的应用程序中

import "github.com/spf13/cobra"

概念

Cobra 建立在命令、参数和标志的结构之上。

命令 代表操作,参数 是事物,标志 是这些操作的修饰符。

最好的应用程序在使用时会像句子一样易于理解。用户会知道如何使用该应用程序,因为他们会本能地理解如何使用它。

要遵循的模式是 APPNAME VERB NOUN --ADJECTIVE.APPNAME COMMAND ARG --FLAG

一些好的现实世界示例可以更好地说明这一点。

在以下示例中,‘server’ 是一个命令,而 ‘port’ 是一个标志

hugo server --port=1313

在此命令中,我们告诉 Git 克隆该 URL 为裸库。

git clone URL --bare

命令是应用程序的中心点。应用程序支持的每个交互都将包含在一个命令中。一个命令可以有子命令,并且可以选择执行一个操作。

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

更多关于 cobra.Command 的信息

标志是修改命令行为的一种方式。Cobra 支持完全符合 POSIX 标准的标志,以及 Go flag 包。Cobra 命令可以定义持久到子命令的标志,以及仅对该命令可用的标志。

在上面的示例中,‘port’ 是标志。

标志功能由 pflag 库 提供,它是 flag 标准库的一个分支,它在保持相同接口的同时添加了 POSIX 兼容性。


入门

虽然您可以自由提供自己的组织方式,但通常基于 Cobra 的应用程序将遵循以下组织结构

  ▾ appName/
    ▾ 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 集成到您的应用程序中最简单的方法。

这里您可以找到有关它的更多信息。

要手动实现 Cobra,您需要创建一个简单的 main.go 文件和一个 rootCmd 文件。您还可以根据需要提供其他命令。

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

理想情况下,您将它放在 app/cmd/root.go 中

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`,
  Run: func(cmd *cobra.Command, args []string) {
    // Do Stuff Here
  },
}

func Execute() {
  if err := rootCmd.Execute(); err != nil {
    fmt.Println(err)
    os.Exit(1)
  }
}

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

例如 cmd/root.go

import (
  "fmt"
  "os"

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

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 函数执行它。为了清晰起见,Execute 应该在根目录上运行,尽管它可以在任何命令上调用。

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

package main

import (
  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

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

如果您想创建一个版本命令,您将创建 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

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

标志可以是‘持久’的,这意味着此标志将对它被分配到的命令以及该命令下的所有命令可用。对于全局标志,将标志分配为根目录上的持久标志。

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"))
}

在此示例中,持久标志 authorviper 绑定。注意,当用户没有提供 --author 标志时,变量 author 不会设置为来自配置的值。

更多内容请参见 viper 文档

标志默认情况下是可选的。如果您希望命令在未设置标志时报告错误,请将其标记为必需标志

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

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

以下验证器是内置的

  • NoArgs - 如果存在任何位置参数,命令将报告错误。
  • ArbitraryArgs - 命令将接受任何参数。
  • OnlyValidArgs - 如果存在任何不在 CommandValidArgs 字段中的位置参数,命令将报告错误。
  • MinimumNArgs(int) - 如果位置参数少于 N 个,命令将报告错误。
  • MaximumNArgs(int) - 如果位置参数多于 N 个,命令将报告错误。
  • ExactArgs(int) - 如果位置参数不正好是 N 个,命令将报告错误。
  • ExactValidArgs(int) = 如果位置参数不正好是 N 个,或者存在任何不在 CommandValidArgs 字段中的位置参数,命令将报告错误
  • RangeArgs(min, max) - 如果参数数量不在预期参数数量的最小值和最大值之间,命令将报告错误。

设置自定义验证器的示例

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’ 来实现的。

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

有关标志的更多文档,请访问 https://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 anything to the screen more times",
    Long: `echo things multiple times back to the user by 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

当您有子命令时,Cobra 会自动将帮助命令添加到您的应用程序中。当用户运行 ‘app help’ 时,将调用此命令。此外,help 还将支持所有其他命令作为输入。例如,假设您有一个名为 ‘create’ 的命令,没有任何其他配置;当调用 ‘app help create’ 时,Cobra 将起作用。每个命令都将自动添加 ‘–help’ 标志。

以下输出由 Cobra 自动生成。除了命令和标志定义之外,不需要任何其他内容。

$ cobra help

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:
  cobra [command]

Available Commands:
  add         Add a command to a Cobra Application
  help        Help about any command
  init        Initialize a Cobra Application

Flags:
  -a, --author string    author name for copyright attribution (default "YOUR NAME")
      --config string    config file (default is $HOME/.cobra.yaml)
  -h, --help             help for cobra
  -l, --license string   name of license for the project
      --viper            use Viper for configuration (default true)

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

帮助只是一个命令,就像其他任何命令一样。它周围没有特殊的逻辑或行为。事实上,如果您愿意,您可以提供自己的帮助命令。

您可以提供您自己的 Help 命令或您自己的模板,供默认命令使用以下函数

cmd.SetHelpCommand(cmd *Command)
cmd.SetHelpFunc(f func(*Command, []string))
cmd.SetHelpTemplate(s string)

后两个函数也将应用于任何子命令。

当用户提供无效标志或无效命令时,Cobra 会通过向用户显示 ‘用法’ 来响应。

您可能从上面的帮助信息中认出了这一点。这是因为默认帮助将用法嵌入到其输出中。

$ cobra --invalid
Error: unknown flag: --invalid
Usage:
  cobra [command]

Available Commands:
  add         Add a command to a Cobra Application
  help        Help about any command
  init        Initialize a Cobra Application

Flags:
  -a, --author string    author name for copyright attribution (default "YOUR NAME")
      --config string    config file (default is $HOME/.cobra.yaml)
  -h, --help             help for cobra
  -l, --license string   name of license for the project
      --viper            use Viper for configuration (default true)

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

您可以提供您自己的用法函数或模板,供 Cobra 使用。与帮助命令一样,函数和模板可以通过公共方法覆盖

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

如果在根命令上设置了 Version 字段,Cobra 会添加一个顶层 ‘–version’ 标志。使用 ‘–version’ 标志运行应用程序将使用版本模板将版本打印到标准输出。可以使用 cmd.SetVersionTemplate(s string) 函数自定义模板。

可以在命令的主要 Run 函数之前或之后运行函数。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)
    },
  }

  var 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]

当发生“未知命令”错误时,Cobra 会打印自动建议。这使得 Cobra 在发生拼写错误时与 git 命令的行为类似。例如

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

Did you mean this?
        server

Run 'hugo --help' for usage.

建议是根据注册的每个子命令自动生成的,并使用 Levenshtein 距离 的实现。每个注册的命令,如果与最小距离 2(忽略大小写)匹配,将显示为建议。

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

command.DisableSuggestions = true

command.SuggestionsMinimumDistance = 1

您还可以使用 SuggestFor 属性显式设置给定命令将被建议的名称。这允许对在字符串距离方面不接近的字符串进行建议,但在您的命令集中是有意义的,并且对于某些您不希望使用别名的命令也是有意义的。例如

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

Did you mean this?
        delete

Run 'kubectl help' for usage.

Cobra 可以根据子命令、标志等生成以下格式的文档

Cobra 可以生成一个 bash 自动补全文件。如果您向命令添加更多信息,这些自动补全功能将变得非常强大和灵活。在 Bash 自动补全 中了解更多信息。


许可证

Cobra 采用 Apache 2.0 许可证发布。请查看 LICENSE.txt