一架梯子,一头程序猿,仰望星空!
Go Cobra教程 > 内容正文

Go Cobra快速入门


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

安装

使用Cobra非常简单。首先,使用go get命令安装最新版本的库。该命令将安装cobra生成器可执行文件以及库和其依赖项:

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

然后,在您的应用程序中引用Cobra:

import "github.com/spf13/cobra"

入门指南

虽然您可以根据自己的组织结构来使用Cobra,但通常基于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提供了一个可以创建您的应用程序并添加任何所需命令的程序。这是将Cobra集成到应用程序中的最简单方法。

这里您可以找到更多相关信息。

使用Cobra库

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

创建rootCmd

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

理想情况下,将此放置在app/cmd/root.go文件中:

var rootCmd = &cobra.Command{
  Use:   "hugo",
  Short: "Hugo是一个非常快速的静态网站生成器",
  Long: `一个由spf13和Go语言朋友们用爱心构建的快速灵活的静态网站生成器。
                完整的文档可在 http://hugo.spf13.com 找到`,
  Run: func(cmd *cobra.Command, args []string) {
    // 在这里执行相关操作
  },
}

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", "", "配置文件(默认为$HOME/.cobra.yaml)")
  rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "基本项目目录,例如github.com/spf13/")
  rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "版权归属的作者姓名")
  rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "项目使用的许可证名称(可以在配置中提供`licensetext`)")
  rootCmd.PersistentFlags().Bool("viper", true, "使用Viper进行配置")
  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 ")
  viper.SetDefault("license", "apache")
}

func initConfig() {
  // 不要忘记从cfgFile或家目录读取配置!
  if cfgFile != "" {
    // 使用命令行标志中的配置文件。
    viper.SetConfigFile(cfgFile)
  } else {
    // 查找目录。
    home, err := homedir.Dir()
    if err != nil {
      fmt.Println(err)
      os.Exit(1)
    }

    // 在家目录中搜索名为".cobra"的配置文件(没有扩展名)。
    viper.AddConfigPath(home)
    viper.SetConfigName(".cobra")
  }

  if err := viper.ReadInConfig(); err != nil {
    fmt.Println("无法读取配置:", err)
    os.Exit(1)
  }
}

创建main.go

使用root命令,需要在main函数中执行它。为了清晰起见,应该在root上调用Execute,但是可以在任何命令中调用它。

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

package main

import (
  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

创建附加命令

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

例如,如果要创建一个version命令,应该创建cmd/version.go文件,并填充以下内容。

package cmd

import (
  "fmt"

  "github.com/spf13/cobra"
)

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

var versionCmd = &cobra.Command{
  Use:   "version",
  Short: "打印Hugo版本号",
  Long:  `所有软件都有版本号。这就是Hugo的版本号`,
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("Hugo静态网站生成器 v0.9 -- HEAD")
  },
}

处理标记

标记提供了修改操作命令行的方式。

为命令分配标记

由于标记在不同的位置定义和使用,我们需要在外部定义一个与作用域相符的变量来分配标记。

var Verbose bool
var Source string

有两种不同的方式来分配标记。

持久标记

标记可以是”persistent”的,这意味着该标记将在分配给它的命令以及该命令下的每个命令中都可用。对于全局标记,在root上分配一个持久标记。

rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "输出详细信息")

局部标记

标记也可以局部分配,只适用于具体的命令。

rootCmd.Flags().StringVarP(&Source, "source", "s", "", "读取的源目录")

父命令上的局部标记

默认情况下,Cobra只解析目标命令上的局部标记,忽略父命令上的局部标记。通过启用“Command.TraverseChildren”,Cobra将在执行目标命令之前解析每个命令上的局部标记。

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

绑定标记和配置

还可以使用viper绑定标记:

var author string

func init() {
  rootCmd.PersistentFlags().StringVar(&author, "author", "你的名字", "版权归属的作者姓名")
  viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
}

在这个例子中,持久标记”author”与”viper”绑定。注意,当用户未提供”—author”标记时,变量”author”的值不会从配置中设置。

更多信息可以查阅viper文档

必需的标记

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

rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS区域(必需)")
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("至少需要一个参数")
    }
    if myapp.IsValidColor(args[0]) {
      return nil
    }
    return fmt.Errorf("指定的颜色无效:%s", args[0])
  },
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("Hello, World!")
  },
}

示例

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

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

package main

import (
  "fmt"
  "strings"

  "github.com/spf13/cobra"
)

func main() {
  var echoTimes int

  var cmdPrint = &cobra.Command{
    Use:   "print [string to print]",
    Short: "将任何内容打印到屏幕上",
    Long: `print用于将任何内容打印回屏幕上。
多年来,人们一直把打印回屏幕上。`,
    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: "将任何内容回显到屏幕上",
    Long: `echo用于回显任何内容。
echo的工作方式与print类似,只是它有一个子命令。`,
    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: "将任何内容多次回显到屏幕上",
    Long: `通过提供计数和一个字符串,多次将内容回显给用户。`,
    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, "要回显输入的次数")

  var rootCmd = &cobra.Command{Use: "app"}
  rootCmd.AddCommand(cmdPrint, cmdEcho)
  cmdEcho.AddCommand(cmdTimes)
  rootCmd.Execute()
}

帮助命令

当应用程序具有子命令时,Cobra会自动为应用程序添加一个帮助命令,当用户运行’app help’时将执行该命令。此外,帮助还支持所有其他命令作为输入。例如,假设您有一个名为’create’的命令且没有任何其他配置,当调用’app help create’时,Cobra将有效地工作。每个命令将自动添加’—help’标志。

示例

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

$ cobra help

Cobra是一个用于Go语言的CLI库,它为应用程序提供了强大的功能。
这个应用程序是一个工具,用于快速创建Cobra应用程序所需的文件。

用法:
  cobra [command]

可用命令:
  add         向Cobra应用程序添加一个命令
  help        查看任意命令的帮助信息
  init        初始化一个Cobra应用程序

标志:
  -a, --author string    版权归属的作者名(默认为“YOUR NAME”)
      --config string    配置文件(默认为$HOME/.cobra.yaml)
  -h, --help             显示Cobra的帮助信息
  -l, --license string   项目所使用的许可证名称
      --viper            使用Viper进行配置(默认为true)

使用“cobra [command] --help”获取有关命令的详细信息。

帮助命令只是像其他命令一样的命令。实际上,您可以根据需要提供自己的帮助命令。

定义您自己的帮助命令

您可以通过以下函数提供您自己的Help命令或默认命令的模板:

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

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

用法消息

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

示例

您可能会从上面的帮助内容中识别出这一点。这是因为默认的帮助会将用法作为其输出的一部分嵌入其中。

$ cobra --invalid
Error: unknown flag: --invalid
用法:
  cobra [command]

可用命令:
  add         向Cobra应用程序添加一个命令
  help        查看任意命令的帮助信息
  init        初始化一个Cobra应用程序

标志:
  -a, --author string    版权归属的作者名(默认为“YOUR NAME”)
      --config string    配置文件(默认为$HOME/.cobra.yaml)
  -h, --help             显示Cobra的帮助信息
  -l, --license string   项目所使用的许可证名称
      --viper            使用Viper进行配置(默认为true)

使用“cobra [command] --help”获取有关命令的详细信息。

定义您自己的用法

您可以为Cobra提供自己的用法函数或模板。与帮助一样,这些函数和模板可以通过公共方法进行重写:

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

版本标志

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

PreRun和PostRun钩子函数

在您的命令的主要“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: "我的根命令",
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("在rootCmd的PersistentPreRun中,参数是:%v\n", args)
    },
    PreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("在rootCmd的PreRun中,参数是:%v\n", args)
    },
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Printf("在rootCmd的Run中,参数是:%v\n", args)
    },
    PostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("在rootCmd的PostRun中,参数是:%v\n", args)
    },
    PersistentPostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("在rootCmd的PersistentPostRun中,参数是:%v\n", args)
    },
  }

  var subCmd = &cobra.Command{
    Use:   "sub [no options!]",
    Short: "我的子命令",
    PreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("在subCmd的PreRun中,参数是:%v\n", args)
    },
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Printf("在subCmd的Run中,参数是:%v\n", args)
    },
    PostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("在subCmd的PostRun中,参数是:%v\n", args)
    },
    PersistentPostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("在subCmd的PersistentPostRun中,参数是:%v\n", args)
    },
  }

  rootCmd.AddCommand(subCmd)

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

输出:

在rootCmd的PersistentPreRun中,参数是:[]
在rootCmd的PreRun中,参数是:[]
在rootCmd的Run中,参数是:[]
在rootCmd的PostRun中,参数是:[]
在rootCmd的PersistentPostRun中,参数是:[]

在rootCmd的PersistentPreRun中,参数是:[arg1 arg2]
在subCmd的PreRun中,参数是:[arg1 arg2]
在subCmd的Run中,参数是:[arg1 arg2]
在subCmd的PostRun中,参数是:[arg1 arg2]
在subCmd的PersistentPostRun中,参数是:[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.