Cobra既是用于创建强大的现代CLI应用程序的库,也是用于生成应用程序和命令文件的程序。

许多最广泛使用的Go项目都是使用Cobra构建的,包括:

Kubernetes
Hugo
rkt
etcd
Moby (former Docker)
Docker (distribution)
OpenShift
Delve
GopherJS
CockroachDB
Bleve
ProjectAtomic (enterprise)
GiantSwarm's swarm
Nanobox/Nanopack
rclone
nehm
Pouch

概述

Cobra是一个库,提供了一个简单的界面来创建类似于git&go工具的强大的现代CLI界面。

Cobra也是一个应用程序,它将生成您的应用程序支架,以快速开发基于Cobra的应用程序。

Cobra提供:

简单易用的基于CLI:app server,app fetch等。
完全符合POSIX标准(包括including short & long versions)
嵌套的子命令
全局,本地和级联标志
易产生的应用程序和命令与cobra init appname&cobra add cmdname
智能建议
命令和标志的自动帮助生成
自动帮助标志识别-h,--help等等。
为您的应用程序自动生成bash自动完成
为您的应用程序自动生成的手册页
命令别名,以便您可以在不破坏它们的情况下进行更改
灵活定义您自己的帮助,用法等。
可选择与viper紧密集成,适用于12-factor应用

概念

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

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

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

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

使用以下示例进行说明,在以下示例中,'server'是一个命令,'port'是一个标志:

hugo server --port=1313

在这个命令中,我们告诉Git克隆url

git clone URL --bare

Commands命令

命令是应用程序的中心点。应用程序支持的每个交互都将包含在命令中。命令可以具有子命令并可选地运行动作。

在上面的示例中,'server'是命令。更多关于cobra.Command

Flags标志

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

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

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

Installing安装

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

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

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

import "github.com/spf13/cobra"

Getting Started入门

虽然欢迎您提供自己的组织,但通常基于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整合到您的应用程序中的最简单方法。

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

使用Cobra Library

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

创建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.go

使用root命令,您需要让main函数执行它。为了清楚起见,应该在root上运行Execute,尽管可以在任何命令上调用它。

在Cobra应用程序中,通常在main.go暴露。它有一个目的,就是初始化Cobra。

package main

import (
  "fmt"
  "os"
  "{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")
  },
}

使用Flags

Flags提供修饰符来控制动作命令的操作方式。

为命令分配Flags

由于flag是在不同的位置定义和使用的,因此我们需要在外部定义一个具有正确范围的变量来分配要使用的标志。

var Verbose bool
var Source string

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

持久的Flags

Flags可以是“持久的”,这意味着该标志可用于它所分配的命令以及该命令下的每个命令。对于全局标志,在根上分配Flag作为持久Flag

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

本地Flags

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

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

父命令上的本地Flags

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

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

使用配置绑定Flags

你也可以用viper绑定你的Flags

var author string

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

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

更多viper文档

必需的Flags

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

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

位置和自定义参数

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

以下验证器内置:

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

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

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)是顶级命令之一的子级。在这种情况下,root不可执行,这意味着需要子命令。这是通过不为'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”时会调用此方法。此外,帮助还将支持所有其他命令作为输入。比如说,你有一个名为'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

您可以提供自己的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使用。与help一样,函数和模板可以通过公共方法覆盖:

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

版本flags

如果在root命令上设置了Version字段,Cobra会添加顶级'--version'标志。使用'--version'标志运行应用程序将使用版本模板将版本打印到stdout。可以使用该cmd.SetVersionTemplate(s string)功能自定义模板 。

PreRun和PostRun Hooks

Run命令在主函数之前或之后运行函数。PersistentPreRunPreRun功能执行之前RunPersistentPostRunPostRun将在后执行RunPersistent*Run如果子程序没有声明他们自己的功能,他们将继承这些功能。这些功能按以下顺序运行:

PersistentPreRun
PreRun
Run
PostRun
PersistentPostRun

下面是使用所有这些功能的两个命令的示例。执行子命令时,它将运行root命令,PersistentPreRun但不运行root命令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.

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐