zoukankan      html  css  js  c++  java
  • Go语言---小白入门-命令行库Cobra的使用

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

    Cobra提供的功能:

    • 简易的子命令行模式,如 app server, app fetch等等

    • 完全兼容posix命令行模式

    • 嵌套子命令subcommand

    • 支持全局,局部,串联flags

    • 使用Cobra很容易的生成应用程序和命令,使用cobra create appname和cobra add cmdname

    • 如果命令输入错误,将提供智能建议,如 app srver,将提示srver没有,是否是app server

    • 自动生成commands和flags的帮助信息

    • 自动生成详细的help信息,如app help

    • 自动识别-h,--help帮助flag

    • 自动生成应用程序在bash下命令自动完成功能

    • 自动生成应用程序的man手册

    • 命令行别名

    • 自定义help和usage信息

    • 可选的紧密集成的viper apps

    Cobra是建立在结构的命令、参数和标志之上。命令(Commands)代表操作,参数(Args)和标志(Flags)是这些行动的修饰符。

    APPNAME COMMAND ARG --FLAG

    cobra使用

    1.安装cobra

    首先,使用go get安装最新版本

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

    安装成功之后会生成一个名为cobra的可执行文件:

    xxx@xxx:~$ ls -al $GOPATH/bin | grep cobra

    2.简单使用,demo

    使用Cobra生成应用程序

    假设现在我们要开发一个基于CLI的命令程序,名字为demo。执行如下命令:

    xxx@xxx:~$ cd $GOPATH/src/github.com/spf13/
    xxx@xxx:~/go/src/github.com/spf13$ cobra init demo --pkg-name=github.com/spf13/demo
    Your Cobra applicaton is ready at
    /home/xxx/go/src/github.com/spf13/demo
    
    xxx@xxx:~/go/src/github.com/spf13$ cd demo/

    在$GOPATH/src/github.com/spf13目录下会生成一个demo的文件夹,结构如下:

    xxx@xxx:~/go/src/github.com/spf13/demo$ tree
    .
    ├── cmd
    │   └── root.go
    ├── LICENSE
    └── main.go
    
    1 directory, 3 files

    测试cobra效果:

    xxx@xxx:~/cobra/demo$ go run main.go
    A longer description that spans multiple lines and likely contains
    examples and usage of using your application. For example:
    
    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.
    
    subcommand is required
    exit status 1

    在Cobra应用程序中,通常main.go文件非常空洞。它主要只干一件事:初始化Cobra。

    package main
    
    //import "{pathToYourApp}/cmd"
    import "github.com/spf13/demo/cmd"
    
    func main() {
      cmd.Execute()
    }

    添加子命令

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

    如果要创建版本(version)命令,可以创建cmd/version.go并使用以下内容填充它:

    xxx@xxx:~/go/src/github.com/spf13/demo$ cobra add version
    version created at /home/zhuzhonghua/go/src/github.com/spf13/demo

    此目录结构变更为:

    xxx@xxx:~/go/src/github.com/spf13/demo$ tree
    .
    ├── cmd
    │   ├── root.go
    │   └── version.go
    ├── LICENSE
    └── main.go
    
    1 directory, 4 files

    现在我们来执行以下这个子命令:

    xxx@xxx:~/go/src/github.com/spf13/demo$ go run main.go version
    version called

    生成的version代码如下(cmd/version.go):

    package cmd
    
    import (
        "fmt"
    
        "github.com/spf13/cobra"
    )
    
    // versionCmd represents the version command
    var versionCmd = &cobra.Command{
        Use:   "version",
        Short: "A brief description of your command",
        Long: `A longer description that spans multiple lines and likely contains examples
    and usage of using your command. For example:
    
    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.`,
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("version called")
        },
    }
    
    func init() {
        rootCmd.AddCommand(versionCmd)
    
        // Here you will define your flags and configuration settings.
    
        // Cobra supports Persistent Flags which will work for this command
        // and all subcommands, e.g.:
        // versionCmd.PersistentFlags().String("foo", "", "A help for foo")
    // Cobra supports local flags which will only run when this command
        // is called directly, e.g.:
        // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
    }
    注:命令的名称使用驼峰式命名(camelCase),而不能使用蛇形命名(snake_case)。读者可以自行创建一个蛇形命名的命令来查看一下实际的效果(hint: camelCase to snake_case)。

    完善子命令功能

    修改上面代码中的函数Run:

    Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("version called")
        },

    修改为:

    Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("Version 1.0.0 for demo")
        },

    再次执行go run main.go version,结果如下:

    Version 1.0.0 for demo

    另一个示例

    在上面的cmd/version.go中我们发现在init函数有这么一行代码:rootCmd.AddCommand(versionCmd),这个rootCmd是什么呢?

    package cmd
    
    import (
      "fmt"
      "os"
      "github.com/spf13/cobra"
    
      homedir "github.com/mitchellh/go-homedir"
      "github.com/spf13/viper"
    
    )
    
    
    var cfgFile string
    
    
    // rootCmd represents the base command when called without any subcommands
    var rootCmd = &cobra.Command{
      Use:   "demo",
      Short: "A brief description of your application",
      Long: `A longer description that spans multiple lines and likely contains
    examples and usage of using your application. For example:
    
    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.`,
      // Uncomment the following line if your bare application
      // has an action associated with it:
      //    Run: func(cmd *cobra.Command, args []string) { },
    }
    
    // Execute adds all child commands to the root command and sets flags appropriately.
    // This is called by main.main(). It only needs to happen once to the rootCmd.
    func Execute() {
      if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
      }
    }
    
    func init() {
      cobra.OnInitialize(initConfig)
    
      // Here you will define your flags and configuration settings.
      // Cobra supports persistent flags, which, if defined here,
      // will be global for your application.
    
      rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.demo.yaml)")
    
    
      // Cobra also supports local flags, which will only run
      // when this action is called directly.
      rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
    }
    
    // initConfig reads in config file and ENV variables if set.
    func initConfig() {
      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 ".demo" (without extension).
        viper.AddConfigPath(home)
        viper.SetConfigName(".demo")
      }
    
      viper.AutomaticEnv() // read in environment variables that match
    
      // If a config file is found, read it in.
      if err := viper.ReadInConfig(); err == nil {
        fmt.Println("Using config file:", viper.ConfigFileUsed())
      }
    }

    打开cmd/root.go(上面的示例代码),你会发现rootCmd其实就是我们的根命令。我相信机智的同学已经猜出来我们添加子命令的子命令的方法了。现在让我们在cmd目录下新建help.go文件,项目文件结构为:

    xxx@xxx:~/go/src/github.com/spf13/demo$ tree
    .
    ├── cmd
    │   ├── help.go
    │   ├── root.go
    │   └── version.go
    ├── LICENSE
    └── main.go
    
    1 directory, 5 files

    其中cmd/help.go的内容为(help为version的子命令)

    package cmd
    
    import (
        "fmt"
    
        "github.com/spf13/cobra"
    )
    
    // versionCmd represents the version command
    var helpCmd = &cobra.Command{
        Use:   "help",
        Short: "show command info",
        Long: `<snip>`,
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("Here is the help information")
        },
    }
    
    func init() {
        versionCmd.AddCommand(helpCmd) //注意这里是verisonCmd不是rootCmd
    }

    执行go run main.go version help命令之后,结果为:

    xxx:demo xxx$ go run main.go version help
    Here is the help information

    3.使用Flags

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

    Persistent Flags:全局性flag, 可用于它所分配的命令以及该命令下的每个命令。在根上分配标志作为全局flag。

    Local Flags:局部性flag,在本args分配一个标志,该标志仅适用于该特定命令。

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

    添加Flags

    如果仔细看过上面cmd/version.go中init函数中的注释的话,你应该已经得到了足够多的信息来自己操作添加flag。

    不过这里我再解释一下,首先是persistent参数,当你的参数作为persistent flag存在时,如注释所言,在其所有的子命令之下该参数都是可见的。而local flag则只能在该命令调用时执行。

    可以做一个简单的测试,在cmd/version.go的init函数中,添加如下内容(添加在rootCmd.AddCommand(versionCmd)这一行之上):

    versionCmd.PersistentFlags().String("global_foo", "global_val", "A help for global_foo")
    versionCmd.Flags().String("local_foo","local_val", "A help for local_foo")

    现在运行go run main.go version -h得到如下结果:

    hidden:demo hidden$ go run main.go version -h
    A longer description that spans multiple lines and likely contains examples
    and usage of using your command. For example:
    
    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:
      demo version [flags]
      demo version [command]
    
    Available Commands:
      help        show command info
    
    Flags:
          --global_foo string   A help for global_foo (default "global_val")
      -h, --help                help for version
          --local_foo string    A help for local_foo (default "local_val")
    
    Global Flags:
          --config string   config file (default is $HOME/.demo.yaml)
    
    Use "demo version [command] --help" for more information about a command.

    接着我们对比着再运行go run main.go help -h试试:

    hidden:demo hidden$ go run main.go version help -h
    <snip>
    
    Usage:
      demo version help [flags]
    
    Flags:
      -h, --help   help for help
    
    Global Flags:
          --config string       config file (default is $HOME/.demo.yaml)
          --global_foo string   A help for global_foo (default "global_val")

    可以发现在Gloabal Flags的变化。version作为root的子命令,仍然可以使用root的persistent flag-> config(可以查看root.go),而help作为test的子命令,不仅可以使用test的persistent flag-> fool, 也可以使用test父命令的persistent flag。从而我们可以直观的看出persistent的作用范围是该命令之后的所有子命令。

    flag支持的参数类型可以参考文档:https://godoc.org/github.com/spf13/cobra

    注意:cmd.Flags().String()与 cmd.Flags().StringP()是不一样的。假如我们在version.go的init下增加如下两行:

    versionCmd.Flags().String("flag1","", "flag1 usage")
    versionCmd.Flags().StringP("flga2","f","", "flag2 usage")

    前者调用需要如下形式:

    go run main.go version --flag1

    后者有如下两种形式调用:

    go run main.go version --flag2
    go run main.go version -f
    其它示例:
    
    persistent flag:
    
    rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
    
    local flag:
    
    rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
    
    rootCmd.MarkFlagRequired("source") // Flags默认是可选的。如果您希望命令在未设置Flags时报告错误,请将其标记为必需

    获取Flags值

    在知道了如何设置参数后,我们的下一步当然是需要在运行时获取改参数的值。现在我们把注意力放到version.go的这个部分:

    var versionCmd = &cobra.Command{
        Use:   "version",
        Short: "A brief description of your command",
        Long: `A longer description that spans multiple lines and likely contains examples
    and usage of using your command. For example:
    
    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.`,
        Run: func(cmd *cobra.Command, args []string) {
            //待下面的示例插入
            fmt.Println("Version 1.0.0 for demo")
        },
    }

    让我们把注意力重新放到上面的代码上。我们也很容易可以猜测到Use,Short,Long三个参数的作用,这里便不做阐述。

    显而易见,我们应该在Run这里来获取参数并执行我们的命令功能。获取参数其实也并不复杂。以versionCmd.Flags().StringP("flag2", "f", "", "flag2 usage")此为例,我们可以在Run函数里添加:

    str,_ := cmd.Flags().GetString("flag2")
    fmt.Printf("The param vale is %s
    ", str)

    运行命令go run main.go version -f vvvv,获得结果如下:

    The param vale is vvvv
    Version 1.0.0 for demo

    使用Args

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

    以下验证器内置:

    • NoArgs - 如果存在任何位置参数,该命令将报告错误。

    • ArbitraryArgs - 该命令将接受任何args。

    • OnlyValidArgs- 如果存在任何不在ValidArgs字段中的位置参数,该命令将报告错误Command。

    • MinimumNArgs(int) - 如果没有至少N个位置参数,该命令将报告错误。

    • MaximumNArgs(int) - 如果有多于N个位置参数,该命令将报告错误。

    • ExactArgs(int) - 如果没有确切的N位置参数,该命令将报告错误。

    • RangeArgs(min, max) - 如果args的数量不在预期args的最小和最大数量之间,则该命令将报告错误。

    为了演示Args的用法,我们在cmd目录下再创建一个args.go文件,其内容如下(注意多了一个Args函数):

    import (
        "fmt"
        "github.com/pkg/errors"
        "github.com/spf13/cobra"
    )
    
    var argsCmd = &cobra.Command{
        Use:   "args",
        Short: "args demo",
        Long: `<snip>`,
        Args: func(cmd *cobra.Command, args []string) error {
            if len(args)<1{
                return errors.New("requires at least one arg")
            }
            return nil
        },
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("args called, args: ", args)
        },
    }
    
    func init() {
        rootCmd.AddCommand(argsCmd)
    }

    示例中限定参数的格式至少为一个否则会报错。我们来运行一下看一看结果如何,首先是不添加参数(go run main.go args):

    xxx:demo hidden$ go run main.go args
    Error: requires at least one arg
    Usage:
      demo args [flags]
    
    Flags:
      -h, --help   help for args
    
    Global Flags:
          --config string   config file (default is $HOME/.demo.yaml)
    
    requires at least one arg
    exit status 1

    可以看到报错:Error: requires at least one arg。

    我们再来试一下添加参数的结果:

    xxx:demo hidden$ go run main.go args 1
    args called, args:  [1]
    xxx:demo hidden$ go run main.go args 1 2 3 4
    args called, args:  [1 2 3 4]

    示例中的Args函数可以替换为

    Args: cobra.MinimumNArgs(1),

    读者可以自行验证一下效果。

    Help命令

    前面的示例中出现了cmd/help.go,为了不产生迷惑,我们把这个文件先删除掉。

    当您有子命令时,Cobra会自动为您的应用程序添加一个帮助命令。当用户运行“app help”时会调用此方法。此外,帮助还将支持所有其他命令作为输入。比如说,你有一个名为'create'的命令,没有任何额外的配置; 当'app help create'被调用时,Cobra会工作。每个命令都会自动添加' - help'标志。

    可以在终端输入cobra或者cobra help命令看一下实际的效果:

    xxx@xxx:~$ 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)

    后两者也适用于子命令。

    Usage

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

    示例:

    xxx@xxx:~$ 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.

    自定义Usage

    您可以提供自己的使用功能或模板供Cobra使用。与Help一样,函数和模板可以通过公共方法覆盖:

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

    PreRun 和 PostRun钩子

    我们可以在Run方法之前或者之后运行一些其它的方法(函数)。PersistentPreRun和PreRun在Run之前执行。PersistentPostRun和PostRun将Run之后执行。Persistent***Run如果子程序没有声明他们自己的功能,他们将继承这些功能。这些功能按以下顺序运行:

    PersistentPreRun
    PreRun
    Run
    PostRun
    PersistentPostRun

    下面是使用所有这些功能的两个命令的示例。执行子命令时,它将运行root命令的PersistentPreRun,但不运行root命令的PersistentPostRun:

    import (
        "fmt"
        "github.com/spf13/cobra"
    )
    
    func main(){
        var rootCmd = &cobra.Command{
            Use: "root",
            PersistentPreRun: func(cmd *cobra.Command, args []string) {
                fmt.Printf("[root] PersistentPreRun with args: %v 
    ", args)
            },
            PreRun: func(cmd *cobra.Command, args []string) {
                fmt.Printf("[root] PreRun with args: %v 
    ", args)
            },
            Run: func(cmd *cobra.Command, args []string) {
                fmt.Printf("[root] Run with args: %v 
    ", args)
            },
            PostRun: func(cmd *cobra.Command, args []string) {
                fmt.Printf("[root] PostRun with args: %v 
    ", args)
            },
            PersistentPostRun: func(cmd *cobra.Command, args []string) {
                fmt.Printf("[root] PersistentPostRun with args: %v 
    ", args)
            },
        }
    
    var subCmd = &cobra.Command{
            Use:   "sub",
            PreRun: func(cmd *cobra.Command, args []string) {
                fmt.Printf("[sub] PreRun with args: %v 
    ", args)
            },
            Run: func(cmd *cobra.Command, args []string) {
                fmt.Printf("[sub] Run with args: %v 
    ", args)
            },
            PostRun: func(cmd *cobra.Command, args []string) {
                fmt.Printf("[sub] PostRun with args: %v 
    ", args)
            },
            PersistentPostRun: func(cmd *cobra.Command, args []string) {
                fmt.Printf("[sub] PersistentPostRun with args: %v 
    ", args)
            },
        }
    
        rootCmd.AddCommand(subCmd)
        rootCmd.SetArgs([]string{""})
        rootCmd.Execute()
        fmt.Println()
        rootCmd.SetArgs([]string{"sub", "arg1", "arg2",})
        rootCmd.Execute()
    }

    运行结果:

    //执行root命令
    [root] PersistentPreRun with args: []
    [root] PreRun with args: []
    [root] Run with args: []
    [root] PostRun with args: []
    [root] PersistentPostRun with args: []
    
    
    //执行sub子命令
    [root] PersistentPreRun with args: [arg1 arg2]
    [sub] PreRun with args: [arg1 arg2]
    [sub] Run with args: [arg1 arg2]
    [sub] PostRun with args: [arg1 arg2]
    [sub] PersistentPostRun with args: [arg1 arg2]

    参考资料及衍生读物

    Golang: Cobra命令行参数库的使用【博客园】
    
    golang命令行库Cobra的使用【简书】
    
    Cobra Generator
    
    https://golang.org/pkg/flag/
    
    https://godoc.org/github.com/spf13/cobra#Command
    
    https://github.com/spf13/pflag
    
    https://godoc.org/github.com/spf13/cobra
    
    github.com/spf13/cobra go cobra包介绍
    
    golang命令行库cobra使用【博客园】
    
    微信公众号:朱小厮的博客
  • 相关阅读:
    3. 动态代理Mapper实现类(官方推荐方式)
    2. MyBatis快速入门
    1.Mybaits底层执行过程
    RabbitMQ安装(windows环境)
    消息中间件(一)MQ详解及四大MQ比较
    Nginx location正则表达式
    SpringBoot
    网络协议--HTTPS
    spring--根据执行jar包的参数使用不同的环境配置
    python读取配置文件模块ConfigParser
  • 原文地址:https://www.cnblogs.com/Paul-watermelon/p/12188390.html
Copyright © 2011-2022 走看看