COBRA

2017/02/17 Tool 共 14499 字,约 42 分钟

COBRA既是一个用来创建强大的现代CLI命令行的GOLANG库,也是一个生成程序应用和命令行文件的程序

cobra

git repo

  • https://github.com/spf13/cobra

示例

cobra简介

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

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

在安装的过程中,由于GWF(长城防火墙)的原因,golang的资源无法访问

  • 解决的思路:git clone 源代码,在GOPATH的src目录中建立相关目录,将clone的源码拷贝进去
  • 注意:目录的名称会有变化
  • 举例如下:
    1. 直接找到github上的源码地址,Git clone https://github.com/go-yaml/yaml.git
    2. 在src 目录下 创建 gopkg.in 目录 把yaml 重命名为yaml.v2

little example

package main

import (
    "fmt"

    "github.com/spf13/cobra"
)

func main() {
    
    var name string
    var food string
    var myFood string

    // 1. 主命令
    var rootCmd = &cobra.Command{
        Use:   "personLikeEat",
        Short: "Input the chinese name and food name",
        //命令执行的函数
        Run: func(cmd *cobra.Command, args []string) {
            if len(name) == 0 {
                cmd.Help()
                return
            }
            if len(food) == 0 {
                cmd.Help()
                return
            }
            fmt.Printf("%s like eat %s.\n", name, food)
        },
    }
    
    // 2. 子命令
    var subCmd = &cobra.Command{
        Use:   "ILikeEat",
        Short: "Input food information",
        //命令执行的函数
        Run: func(cmd *cobra.Command, args []string) {
            if len(myFood) == 0 {
                cmd.Help()
                return
            }
            fmt.Printf("I like eat %s.\n", myFood)
        },
    }
    
    // 添加子命令
    rootCmd.AddCommand(subCmd)
    
    // 3.1 主命令添加选项
    rootCmd.Flags().StringVarP(&name, "name", "n", "", "person's name")
    rootCmd.Flags().StringVarP(&food, "food", "f", "", "food's name")
    
    // 3.2 子命令添加选项
    subCmd.Flags().StringVarP(&myFood, "food", "f", "", "food's name")
    
    // 执行命令
    rootCmd.Execute()
}

运行结果如下:

E:\mygo\src\test\demo\personLikeEat>personLikeEat
Input the chinese name and food name

Usage:
  personLikeEat [flags]
  personLikeEat [command]

Available Commands:
  ILikeEat    Input food information

Flags:
  -f, --food string   food's name
  -n, --name string   person's name

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

E:\mygo\src\test\demo\personLikeEat>personLikeEat -n yp -f apple
yp like eat apple.

E:\mygo\src\test\demo\personLikeEat>personLikeEat ILikeEat
Input food information

Usage:
  personLikeEat ILikeEat [flags]

Flags:
  -f, --food string   food's name

E:\mygo\src\test\demo\personLikeEat>personLikeEat ILikeEat -f banana
I like eat banana.

基本用法就是这四步:

  1. 主命令
  2. 子命令
  3. 添加选项
  4. 执行命令

使用cobra生成应用程序

假设现在我们要开发一个基于CLIs的命令程序,名字为cobraDemo。首先打开CMD,切换到GOPATH的src目录下,执行如下指令:

E:\mygo\src>..\bin\cobra.exe init cobraDemo
Your Cobra application is ready at
E:\mygo\src\cobraDemo
Give it a try by going there and running `go run main.go`
Add commands to it by running `cobra add [cmdname]`

在src目录下会生成一个cobraDemo的文件夹,如下:

cobraDemo
│  LICENSE
│  main.go
│  
└─cmd
        root.go

如果你的cobraDemo程序没有subcommands,那么cobra生成应用程序的操作就结束了。

如何实现没有子命令的CLIs程序

接下来就是可以继续cobraDemo的功能设计了。例如我在demo下面新建一个包,名称为imp。如下:

cobraDemo
│  LICENSE
│  main.go
│  
├─cmd
│      root.go
│      
└─imp
        imp.go

imp.go文件的代码如下:

package imp

import(
    "fmt"
)

func Show(name string, age int) {
    fmt.Printf("My Name is %s, My age is %d\n", name, age)
}

cobraDemo程序成命令行接收两个参数name和age,然后打印出来。打开cobra自动生成的main.go文件查看:

// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import "cobraDemo/cmd"

func main() {
    cmd.Execute()
}

可以看出main函数执行cmd包,所以我们只需要在cmd包内调用imp包就能实现demo程序的需求。接着打开root.go文件查看:

// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var cfgFile string

// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
    Use:   "cobraDemo",
    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 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/.cobraDemo.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 != "" { // enable ability to specify config file via flag
        viper.SetConfigFile(cfgFile)
    }

    viper.SetConfigName(".cobraDemo") // name of config file (without extension)
    viper.AddConfigPath("$HOME")  // adding home directory as first search path
    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包进行了一些初始化操作并提供了Execute接口。十分简单,其中viper是cobra集成的配置文件读取的库,这里不需要使用,我们可以注释掉(不注释可能生成的应用程序很大约10M,这里没用到最好是注释掉)。cobra的所有命令都是通过cobra.Command这个结构体实现的。为了实现demo功能,显然我们需要修改RootCmd。修改后的代码如下:

// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
    // "github.com/spf13/viper"
    "cobraDemo/imp"
)

// var cfgFile string
var name string
var age int

// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
    Use:   "cobraDemo",
    Short: "A test demo",
    Long: `Demo is a test appcation for print things`,
    // Uncomment the following line if your bare application
    // has an action associated with it:
    Run: func(cmd *cobra.Command, args []string) {
        if len(name) == 0 {
            cmd.Help()
            return
        }
        imp.Show(name, age)
    },
}

// Execute adds all child commands to the root command 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/.cobraDemo.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")
    RootCmd.Flags().StringVarP(&name, "name", "n", "", "person's name")
    RootCmd.Flags().IntVarP(&age, "age", "a", 0, "person's age")    
}

// initConfig reads in config file and ENV variables if set.
// func initConfig() {
//  if cfgFile != "" { // enable ability to specify config file via flag
//      viper.SetConfigFile(cfgFile)
//  }

//  viper.SetConfigName(".cobraDemo") // name of config file (without extension)
//  viper.AddConfigPath("$HOME")  // adding home directory as first search path
//  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())
//  }
//}

到此demo的功能已经实现了,我们编译运行一下看看实际效果:

E:\mygo\src\cobraDemo>go build

E:\mygo\src\cobraDemo>cobraDemo.exe
Demo is a test appcation for print things

Usage:
  cobraDemo [flags]

Flags:
  -a, --age int       person's age
  -n, --name string   person's name

E:\mygo\src\cobraDemo>cobraDemo.exe -n yp -a 26
My Name is yp, My age is 26

如何实现带有子命令的CLIs程序

在执行cobra.exe init demo之后,继续使用cobra为demo添加子命令test:

E:\mygo\src\cobraDemo>..\..\bin\cobra add test
test created at E:\mygo\src\cobraDemo\cmd\test.go

在src目录下demo的文件夹下生成了一个cmd\test.go文件,如下:

cobraDemo
│  cobraDemo.exe
│  LICENSE
│  main.go
│  
├─cmd
│      root.go
│      test.go
│      
└─imp
        imp.go

接下来的操作就和上面修改root.go文件一样去配置test子命令。效果如下:

E:\mygo\src\cobraDemo>go build

E:\mygo\src\cobraDemo>cobraDemo.exe
Demo is a test appcation for print things

Usage:
  cobraDemo [flags]
  cobraDemo [command]

Available Commands:
  test        A brief description of your command

Flags:
  -a, --age int       person's age
  -n, --name string   person's name

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

可以看出demo既支持直接使用标记flag,又能使用子命令

E:\mygo\src\cobraDemo>cobraDemo.exe test -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:
  cobraDemo test [flags]

调用test命令输出信息,这里没有对默认信息进行修改。

E:\mygo\src\cobraDemo>cobraDemo.exe tet
Error: unknown command "tet" for "cobraDemo"

Did you mean this?
        test

Run 'cobraDemo --help' for usage.
unknown command "tet" for "cobraDemo"

Did you mean this?
        test

这是错误命令提示功能

函数解析

函数说明

cobra doc

关于代码执行顺序

// The *Run functions are executed in the following order:
//   * PersistentPreRun()
//   * PreRun()
//   * Run()
//   * PostRun()
//   * PersistentPostRun()

修改root.go文件如下:

// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
    // "github.com/spf13/viper"
    "cobraDemo/imp"
)

// var cfgFile string
var name string
var age int

// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
    Use:   "cobraDemo",
    Short: "A test demo",
    Long: `Demo is a test appcation for print things`,
    // Uncomment the following line if your bare application
    // has an action associated with it:
    
    // The *Run functions are executed in the following order:
    //   * PersistentPreRun()
    //   * PreRun()
    //   * Run()
    //   * PostRun()
    //   * PersistentPostRun()  
    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) {
        if len(name) == 0 {
            cmd.Help()
            return
        }
        imp.Show(name, age)
    },  
    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)
    },  
}

// Execute adds all child commands to the root command 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/.cobraDemo.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")
    RootCmd.Flags().StringVarP(&name, "name", "n", "", "person's name")
    RootCmd.Flags().IntVarP(&age, "age", "a", 0, "person's age")    
}

测试结果如下:

E:\mygo\src\cobraDemo>go build

E:\mygo\src\cobraDemo>cobraDemo.exe
Inside rootCmd PersistentPreRun with args: []
Inside rootCmd PreRun with args: []
Demo is a test appcation for print things

Usage:
  cobraDemo [flags]
  cobraDemo [command]

Available Commands:
  test        A brief description of your command

Flags:
  -a, --age int       person's age
  -n, --name string   person's name

Use "cobraDemo [command] --help" for more information about a command.
Inside rootCmd PostRun with args: []
Inside rootCmd PersistentPostRun with args: []

E:\mygo\src\cobraDemo>cobraDemo.exe -n yp -a 26
Inside rootCmd PersistentPreRun with args: []
Inside rootCmd PreRun with args: []
My Name is yp, My age is 26
Inside rootCmd PostRun with args: []
Inside rootCmd PersistentPostRun with args: []

E:\mygo\src\cobraDemo>cobraDemo.exe test
Inside rootCmd PersistentPreRun with args: []
test called
Inside rootCmd PersistentPostRun with args: []

当使用–help命令的时候,PersistentPreRun、PreRun、PostRun、PersistentPostRun不会被调用 示例如下:

E:\mygo\src\cobraDemo>cobraDemo.exe --help
Demo is a test appcation for print things

Usage:
  cobraDemo [flags]
  cobraDemo [command]

Available Commands:
  test        A brief description of your command

Flags:
  -a, --age int       person's age
  -n, --name string   person's name

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

“数据类型” + “P”

此种类型支持短标记

// BoolP is like Bool, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BoolP(name, shorthand string, value bool, usage string) *bool {
    p := new(bool)
    f.BoolVarP(p, name, shorthand, value, usage)
    return p
}

“数据类型” + “Var”

此种类型表示变量

// BoolVar defines a bool flag with specified name, default value, and usage string.
// The argument p points to a bool variable in which to store the value of the flag.
func BoolVar(p *bool, name string, value bool, usage string) {
    BoolVarP(p, name, "", value, usage)
}

全局短标记过时

    RootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
    RootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")

示例如下:

E:\mygo\src\cobraDemo>cobraDemo.exe -h
Flag shorthand -h has been deprecated, please use --help
Demo is a test appcation for print things

Usage:
  cobraDemo [flags]
  cobraDemo [command]

Available Commands:
  test        A brief description of your command

Flags:
  -a, --age int       person's age
      --help          Print usage
  -n, --name string   person's name

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

E:\mygo\src\cobraDemo>cobraDemo.exe test -h
Flag shorthand -h has been deprecated, please use --help
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:
  cobraDemo test [flags]

Global Flags:
      --help   Print usage

文档信息

Search

    Table of Contents