Go之现代命令行框架Cobra

2024-07-27 19:21:07 浏览数 (1)

Go之现代命令行框架Cobra

老实说,今天是我第一次见到现代命令行框架这个名词,在此之前,我并不知道这个东西的作用是什么。下面一起来了解一下这个东西。

Cobra 是一个 Go 语言开发的命令行(CLI)框架,它提供了简洁、灵活且强大的方式来创建命令行程序。它包含一个用于创建命令行程序的库(Cobra 库),以及一个用于快速生成基于 Cobra 库的命令行程序工具(Cobra 命令)。Cobra 是由 Go 团队成员 spf13 为 Hugo 项目创建的,并已被许多流行的 Go 项目所采用,如 Kubernetes、Helm、Docker (distribution)、Etcd 等。

概念

Cobra 是建立在命令、参数和标志这三个结构之上的。要使用 Cobra 编写一个命令行程序,需要明确这三个概念。

  • 命令(COMMAND):命令表示要执行的操作。
  • 参数(ARG):是命令的参数,一般用来表示操作的对象。
  • 标志(FLAG):是命令的修饰,可以调整操作的行为。

一个好的命令行程序应当类似与

代码语言:javascript复制
 APPNAME VERB NOUN --ADJECTIVE

在这里 VERB 代表动词,NOUN 代表名词,ADJECTIVE 代表形容词。

或是

代码语言:javascript复制
 APPNAME COMMAND ARG --FLAG

先安装一下

代码语言:javascript复制
 go get -u github.com/spf13/cobra@latest

安装之后就可以正常的导入使用了

创建命令

这里假设创建命令行程序hugo,可以编写如下代码

代码语言:javascript复制
 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 https://gohugo.io`,
   Run: func(cmd *cobra.Command, args []string) {
     fmt.Println("run hugo...")
   },
 }
 ​
 func Execute() {
   if err := rootCmd.Execute(); err != nil {
     fmt.Println(err)
     os.Exit(1)
   }
 }
 ​

定义根命令 rootCmdrootCmd 是一个指向 cobra.Command 结构体的指针,这个结构体定义了命令行工具的一个命令。 Use: "hugo":指定了该命令的使用方法,这里是hugo,这意味着用户在命令行中输入 hugo 时会触发这个命令。 Short: "Hugo is a very fast static site generator":提供了一个简短的描述,当用户输入 hugo -hhugo --help 时会显示这段描述。 Long 字段提供了更详细的描述,包括项目的背景信息和文档链接。这段描述会在用户输入 hugo -hhugo --help 时显示 定义 Run 方法 run是一个函数,它定义了当命令被执行时要运行的代码。这个函数接收两个参数: cmd *cobra.Command:指向当前正在执行的命令。 args []string:包含命令行参数的切片。在这个例子中,Run 方法只是简单地打印了 run hugo...。 定义 Execute 函数 Execute 函数调用 rootCmd.Execute() 来启动命令行解析和执行过程。` rootCmd.Execute()方法会解析命令行输入,找到并执行相应的命令(在这个例子中是rootCmd`)。 如果 Execute 方法返回错误(表示命令执行失败),错误信息将被打印,并且程序将以状态码 1 退出。

然后写一个main文件,作为程序的启动入口

代码语言:javascript复制
 package main
 ​
 import (
   "cobra/cmd"
 )
 ​
 func main() {
   cmd.Execute()
 }
 ​

逻辑很简单,这里只是调用了cmd.Execute()函数来执行命令。

编译并运行

代码语言:javascript复制
 # 编译
 $ go build -o hugo
 # 执行
 $ ./hugo
 run hugo...
 ​

也可以使用-h -help来查看这个命令行程序的使用帮助

代码语言:javascript复制
 # ./hugo -h
 A Fast and Flexible Static Site Generator built with
                 love by spf13 and friends in Go.
                 Complete documentation is available at https://gohugo.io
 ​
 Usage:
   hugo [flags]
 ​
 Flags:
   -h, --help   help for hugo
 ​

这里打印了 cobra.Command 结构体中 Long 属性的内容,如果 Long 属性不存在,则打印 Short 属性内容。

hugo 命令用法为 hugo [flags],如 hugo --help

这个命令行程序自动支持了 -h/--help 标志。

这就是使用 Cobra 编写一个命令行程序最常见的套路,这也是 Cobra 推荐写法。

现在项目结构如下

代码语言:javascript复制
 # tree cobra
 cobra
 ├── cmd
 │   ├── go.mod
 │   ├── go.sum
 │   └── root.go
 ├── go.mod
 ├── go.sum
 ├── hugo
 └── main.go
 ​

Cobra 程序目录结构基本如此,main.go 作为命令行程序的入口,不要写过多的业务逻辑,所有命令都应该放在 cmd/ 目录下,以后不管编写多么复杂的命令行程序都可以这么来设计。

代码语言:javascript复制
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")
  },
}

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

添加子命令

与定义 rootCmd 一样,我们可以使用 cobra.Command 定义其他命令,并通过 rootCmd.AddCommand() 方法将其添加为 rootCmd 的一个子命令。

代码语言:javascript复制
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")
  },
}

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

然后重新编译并运行命令行程序。

代码语言:javascript复制
#go build -o hugo
#./hugo version                       
Hugo Static Site Generator v0.9 -- HEAD

可以看到version已经添加进来了,然后再查看帮助信息

代码语言:javascript复制
A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at https://gohugo.io

Usage:
  hugo [flags]
  hugo [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  version     Print the version number of Hugo

Flags:
  -h, --help   help for hugo

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

这次的帮助信息更为丰富,除了可以使用 hugo [flags] 语法,由于子命令的加入,又多了一个 hugo [command] 语法可以使用,如 hugo version

completion 可以为指定的 Shell 生成自动补全脚本,将在 Shell 补全小节进行讲解。

help用来查看帮助,同 -h/--help 类似,可以使用 hugo help command 语法查看 command 命令的帮助信息。

version为新添加的子命令。

查看子命令的帮助信息

代码语言:javascript复制
# ./hugo help version
All software has versions. This is Hugo's

Usage:
  hugo version [flags]

Flags:
  -h, --help   help for version

命令行标志

持久标志

如果一个标志是持久的,那么这个标志将可用于它所分配的命令以及该命令下的所有子命令。

对于全局标志而言,可以定义在根命令rootcmd上面。

代码语言:javascript复制
var Verbose bool
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")

本地标志

标志也可以是本地的,这意味它可能只能用于指定命令

代码语言:javascript复制
var Source string
rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")

父命令的本地标志

一般默认cobra只解析目标命令上的本地标志,忽略父命令上的本地标志,通过在父命令上启动Command.TraverseChildren 属性,Cobra 将在执行目标命令之前解析每个命令的本地标志。

代码语言:javascript复制
var rootCmd = &cobra.Command{
  Use:   "hugo",
  TraverseChildren: true,
}

必选标志

默认情况下,标志是可选的,我们可以将其设置为必选,如果没有提供这个标志,就会报错。

代码语言:javascript复制
var Region string
rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
rootCmd.MarkFlagRequired("region")

定义好以上几个标志后,为了展示效果,依次对 rootCmd.Run 方法做些修改,分别打印 VerboseSourceRegion 几个变量。

代码语言:javascript复制
var rootCmd = &cobra.Command{
  Use:   "hugo",
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("run hugo...")
    fmt.Printf("Verbose: %vn", Verbose)
    fmt.Printf("Source: %vn", Source)
    fmt.Printf("Region: %vn", Region)
  },
}

另外,为了测试启用 Command.TraverseChildren 的效果,再添加一个 print 子命令。

代码语言:javascript复制
var printCmd = &cobra.Command{
  Use: "print [OPTIONS] [COMMANDS]",
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("run print...")
    fmt.Printf("printFlag: %vn", printFlag)
    fmt.Printf("Source: %vn", Source)
  },
}

func init() {
  rootCmd.AddCommand(printCmd)

  // 本地标志
  printCmd.Flags().StringVarP(&printFlag, "flag", "f", "", "print flag for local")
}

然后编译运行

代码语言:javascript复制
#go build -o hugo
#./hugo -h                    
A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at https://gohugo.io
Usage:
  hugo [flags]
  hugo [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  print       
  version     Print the version number of Hugo

Flags:
  -h, --help            help for hugo
  -r, --region string   AWS region (required)
  -s, --source string   Source directory to read from
  -v, --verbose         verbose output

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

执行hugo

现在 -r/--region 为必选标志,不传将会得到 Error: required flag(s) "region" not set 报错。

代码语言:javascript复制
# ./hugo -r test-region
run hugo...
Verbose: false
Source: 
Region: test-region

执行print子命令

代码语言:javascript复制
# ./hugo print -f test-flag
run print...
printFlag: test-flag
Source: 

父命令的标志 Source 内容为空。

再看一条命令

代码语言:javascript复制
# ./hugo -s test-source print -f test-flag
run print...
printFlag: test-flag
Source: test-source

print 子命令前,我们指定了 -s test-source 标志,-s/--source 是父命令 hugo 的标志,也能够被正确解析,这就是启用 Command.TraverseChildren 的效果。 如果我们将 rootCmdTraverseChildren 属性置为 false,则会得到 Error: unknown shorthand flag: 's' in -s 报错。

使用cobra命令创建项目

cobra的目录结构,文件,模板代码都是比较固定的

这个时候脚手架工具就派上了用场,cobra提供了cobra-cli这个工具,可以通过命令的方式快速创建一个命令行项目。

安装

代码语言:javascript复制
go install github.com/spf13/cobra-cli@latest

使用帮助

代码语言:javascript复制
cobra-cli -h       
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-cli [command]

Available Commands:
  add         Add a command to a Cobra Application
  completion  Generate the autocompletion script for the specified shell
  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-cli
  -l, --license string   name of license for the project
      --viper            use Viper for configuration

Use "cobra-cli [command] --help" for more information about a command.
初始化模块

要使用 cobra-cli 生成一个项目,首先要手动创建项目根目录并使用 go mod 命令进行初始化。

代码语言:javascript复制
# 创建项目目录
$ mkdir cob
# 进入项目目录
$ cd cob
# 初始化模块
$ go mod init cob
初始化命令行程序

初始化好 Go 项目,我们就可以初始化命令行程序了。

代码语言:javascript复制
# cobra-cli init
Your Cobra application is ready at
/f1sh/data/cob
root@f1sh:/f1sh/data/cob# tree .
.
├── cmd
│   └── root.go
├── go.mod
├── go.sum
├── LICENSE
└── main.go

2 directories, 5 files
root@f1sh:/f1sh/data/cob# ls
cmd  go.mod  go.sum  LICENSE  main.go
root@f1sh:/f1sh/data/cob# 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.

初始化的时候可能会出现cobra-cli init 命令不存在报错,可以通过配置环境变量或者使用绝对路径来解决 export PATH="~/go/bin:$PATH"

使用 cobra-cli 初始化程序非常方便,只需要一个简单的 init 命令即可完成

目录结构跟我们手动编写的程序相同,只不过多了一个 LICENSE 文件,用来存放项目的开源许可证。

通过 go run main.go 执行这个命令行程序,即可打印 rootCmd.Run 的输出结果。

脚手架自动生成的main.go如下

代码语言:javascript复制
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>

*/
package main

import "cob/cmd"

func main() {
	cmd.Execute()
}

自动生成的root.go如下

代码语言:javascript复制
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>

*/
package cmd

import (
	"os"

	"github.com/spf13/cobra"
)



// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "cob",
	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() {
	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}

func init() {
	// 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/.cob.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")
}

以上两个文件跟我们手动编写的代码没什么两样,套路完全相同,唯一不同的是每个文件头部都会多出来一个 Copyright 头信息,用来标记代码的 LICENSE

可选标志

cobra-cli 提供了如下三个标志分别用来设置项目的作者、许可证类型、是否使用 Viper 管理配置。

代码语言:javascript复制
$ cobra-cli init --author "jianghushinian" --license mit --viper
Your Cobra application is ready at
/*以上命令我们指定可选标志后对项目进行了重新初始化。
现在 LICENSE 文件内容不再为空,而是 MIT 协议。*/
代码语言:javascript复制
The MIT License (MIT)
Copyright © 2023 jianghushinian
Permission is hereby granted...

同时 Copyright 头信息中作者信息也会被补全。

代码语言:javascript复制
/*
Copyright © 2023 jianghushinian

...
*/
添加命令
代码语言:javascript复制
#cobra-cli add serve
#cobra-cli add config
#cobra-cli add create -p 'configCmd' --author "jianghushinian" --license mit --viper 

使用 -p 'configCmd' 标志指定当前命令的父命令时,configCmd 必须是小驼峰命名法,因为 cobra-cliconfig 生成的命令代码自动命名为 configCmd,而不是 config_cmd 或其他形式,这符合 Go 语言变量命名规范。

可以使用如下命令执行子命令:

代码语言:javascript复制
#go run main.go config create
create called
使用配置取代标志

如果你不想每次生成或添加命令时都指定选项参数,则可以定义 ~/.cobra.yaml 文件来保存配置信息:

代码语言:javascript复制
author: f1sh <f1sh@123.com>
year: 2024
license: MIT
useViper: true

再次使用 init 命令初始化程序:

代码语言:javascript复制
cobra-cli init                                                                   
Using config file: /Users/jianghushinian/.cobra.yaml

会提示使用了 ~/.cobra.yaml 配置文件。

现在 LICENSE 文件内容格式如下:

代码语言:javascript复制
The MIT License (MIT)

Copyright © 2024 f1sh <f1sh@123.com>

...

Copyright 头信息也会包含日期、用户名、用户邮箱。

代码语言:javascript复制
/*
Copyright © 2024 f1sh <f1sh@123.com>

...
*/

如果你不想把配置保存在 ~/.cobra.yaml 中,cobra-cli 还提供了 --config 标志来指定任意目录下的配置文件。

到这里,常用功能就总结的差不多了。cobra是一个功能强大的命令行框架,进一步提高了命令行编写程序的效率。

0 人点赞