Go语言命令行利器cobra使用教程

cobra既是一个用于创建强大现代CLI应用程序的库,也是一个生成应用程序和命令文件的程序。cobra被用在很多go语言的项目中,比如 Kubernetes、Docker、Istio、ETCD、Hugo、Github CLI等等,此列表包含很多广泛使用cobra的项目列表

本文 commands译为命令,flags译为标志

概述

cobra是一个提供简单接口来创建强大的现代CLI界面的库类似git & git tools,cobra也是一个应用程序,它会生成你的应用程序的脚手架来快速开发基于cobra的应用程序
cobra提供:

  • 简单的基于子命令的命令行:app server、app fetch 等等
  • 完全符合POSIX的标志(包含短版本和长版本)
  • 嵌套子命令
  • 全局、本地和级联的标志
  • 使用 cobra init appname和cobra add cmdname 可以很容易生成应用程序和命令
  • 智能提示(app srver... did you mean app server?)
  • 自动生成命令和标志
  • 自动识别 -h --help 等等为help标志
  • 为应用程序自动shell补全(bash、zsh、fish、powershell)
  • 为应用程序自动生成手册
  • 命令别名
  • 灵活定义帮助、用法等等
  • 可选的与viper的紧密集成

概念

cobra建立在命令、参数、标志的结构之上
commands代表动作,args是事物,flags是动作的修饰符
最好的应用程序在使用时读起来就像句子,因此,用户直观地知道如何与它们交互
模式如下:APPNAME VERB NOUN --ADJECTIVE. or APPNAME COMMAND ARG --FLAG(APPNAME 动词 名词 形容词 或者 APPNAME 命令 参数 标志)
一些真实世界的好例子可以更好地说明这一点

kubectl 命令更能体现APPNAME 动词 名词 形容词

kubectl get pods -owide

如下的例子,server 是command,port是flag

hugo server --port=1313

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

git clone URL --bare

Commands

命令是应用程序的中心点,应用程序支持的每一个交互都包含在一个命令中,命令可以有子命令,也可以运行操作
在上面的例子中,server是命令
更多关于cobra.Command

Flags

flag是一种修改命令行为的方式,cobra支持完全兼容POSIX标志,也支持go flag package,cobra可以定义到子命令上的标志,也可以仅对该命令可用的标志
在上面的命令中,port是标志
标志的功能由pflag library提供,pflag library是flag标准库的一个分支,在添加POSIX兼容性的同时维护相同的接口。

Installing

使用cobra很简单,首先,使用go get按照最新版本的库,这个命令会安装cobra可执行程序以及库和依赖项

go get -u github.com/spf13/cobra

下一步,引入cobra到应用程序中

import "github.com/spf13/cobra"

入门

虽然欢迎您提供自己的组织,但通常基于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的更多信息

使用cobra库

要手动实现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.Fprintln(os.Stderr, err)
    os.Exit(1)
  }
}

在init()函数中定义标志和处理配置
例子如下,cmd/root.go:

package cmd

import (
    "fmt"
    "os"

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

var (
    // Used for flags.
    cfgFile     string
    userLicense string

    rootCmd = &cobra.Command{
        Use:   "cobra",
        Short: "A generator for Cobra based Applications",
        Long: `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.`,
    }
)

// Execute executes the root command.
func Execute() error {
    return rootCmd.Execute()
}

func init() {
    cobra.OnInitialize(initConfig)

    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
    rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "author name for copyright attribution")
    rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project")
    rootCmd.PersistentFlags().Bool("viper", true, "use Viper for configuration")
    viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
    viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))
    viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
    viper.SetDefault("license", "apache")

    rootCmd.AddCommand(addCmd)
    rootCmd.AddCommand(initCmd)
}

func initConfig() {
    if cfgFile != "" {
        // Use config file from the flag.
        viper.SetConfigFile(cfgFile)
    } else {
        // Find home directory.
        home, err := homedir.Dir()
        cobra.CheckErr(err)

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

    viper.AutomaticEnv()

    if err := viper.ReadInConfig(); err == nil {
        fmt.Println("Using config file:", viper.ConfigFileUsed())
    }
}

创建main.go
使用root命令,您需要让主函数执行它。为清楚起见,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")
  },
}

返回和处理错误

如果希望将错误返回给命令的调用者,可以使用RunE。

package cmd

import (
  "fmt"

  "github.com/spf13/cobra"
)

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

var tryCmd = &cobra.Command{
  Use:   "try",
  Short: "Try and possibly fail at something",
  RunE: func(cmd *cobra.Command, args []string) error {
    if err := someFunc(); err != nil {
    return err
    }
    return nil
  },
}

然后可以在execute函数调用中捕获错误。

使用Flags

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

为命令指定标志

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

var Verbose bool
var Source string

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

持久标志

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

// v flag将在rootCmd及以下的子命令上都生效
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")

本地标志

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

// 该命令只在localCmd上起作用
localCmd.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的文档

必须的flags

Flags默认是可选的,如果希望命令在未设置标志时报告错误,请根据需要进行标记:

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

持久性Flags

rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
rootCmd.MarkPersistentFlagRequired("region")

位置参数和自定义参数

可以使用命令的Args字段指定位置参数的验证。
内置了以下验证器:

  • NoArgs-如果存在任何位置参数,该命令将报告错误。
  • ArbitraryArgs-该命令将接受任何args。
  • OnlyValidArgs-如果有任何位置参数不在命令的ValidArgs字段中,则该命令将报告错误。
  • MinimumNArgs(int)-如果没有至少N个位置参数,则该命令将报告错误。
  • MaximumNArgs(int)-如果位置参数超过N个,则该命令将报告错误。
  • ExactArgs(int)-如果没有正好N个位置参数,则命令将报告错误。
  • ExactValidArgs(int)-如果没有正好N个位置参数,或者如果有任何位置参数不在命令的ValidArgs字段中,则该命令将报告错误
  • 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 a color argument")
    }
    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("Echo: " + strings.Join(args, " "))
    },
  }

  var cmdTimes = &cobra.Command{
    Use:   "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会自动将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.

帮助就像其他命令一样。它周围没有特殊的逻辑或行为。事实上,你可以提供你想提供的。

自定义帮助

您可以为默认命令提供自己的帮助命令或模板,以用于以下功能:

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)

标志版本

如果在root命令上设置了version字段,Cobra会添加一个顶级的'--version'标志。运行带有“-version”标志的应用程序将使用版本模板将版本打印到标准输出。可以使用cmd.SetVersionTemplate(s string)函数自定义模板。

运行前运行后钩子

可以在命令的主运行函数之前或之后运行函数。PersistentPreRun和PreRun函数将在运行之前执行。PersistentPostRun和PostRun将在运行后执行。如果子函数不声明自己的函数,则它们将继承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

or

command.SuggestionsMinimumDistance = 1

您还可以使用SuggestFor属性显式设置将为其建议给定命令的名称。这允许对在字符串距离方面不接近的字符串提供建议,但在您的一组命令中是有意义的,并且对于某些您不需要别名的字符串。例子:

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

Did you mean this?
        delete

Run 'kubectl help' for usage.

为您的命令生成文档

Cobra可以基于子命令、标志等生成文档。请在docs generation文档中阅读更多关于它的信息。

shell补全

Cobra可以为以下shell生成shell完成文件:bash、zsh、fish、PowerShell。如果您在命令中添加更多信息,这些补全功能将非常强大和灵活。在Shell Completions中阅读更多关于它的信息。

License

Cobra is released under the Apache 2.0 license. See LICENSE.txt

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容