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)
}
}
定义根命令
rootCmd
:rootCmd
是一个指向cobra.Command
结构体的指针,这个结构体定义了命令行工具的一个命令。Use: "hugo"
:指定了该命令的使用方法,这里是hugo
,这意味着用户在命令行中输入hugo
时会触发这个命令。Short: "Hugo is a very fast static site generator"
:提供了一个简短的描述,当用户输入hugo -h
或hugo --help
时会显示这段描述。Long
字段提供了更详细的描述,包括项目的背景信息和文档链接。这段描述会在用户输入hugo -h
或hugo --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/
目录下,以后不管编写多么复杂的命令行程序都可以这么来设计。
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
的一个子命令。
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 将在执行目标命令之前解析每个命令的本地标志。
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
方法做些修改,分别打印 Verbose
、Source
、Region
几个变量。
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
子命令。
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
报错。
# ./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
在
-s test-source
标志,-s/--source
是父命令hugo
的标志,也能够被正确解析,这就是启用Command.TraverseChildren
的效果。 如果我们将rootCmd
的TraverseChildren
属性置为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
命令进行初始化。
# 创建项目目录
$ 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 管理配置。
$ 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
头信息中作者信息也会被补全。
/*
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-cli
为config
生成的命令代码自动命名为configCmd
,而不是config_cmd
或其他形式,这符合 Go 语言变量命名规范。
可以使用如下命令执行子命令:
代码语言:javascript复制#go run main.go config create
create called
使用配置取代标志
如果你不想每次生成或添加命令时都指定选项参数,则可以定义 ~/.cobra.yaml
文件来保存配置信息:
author: f1sh <f1sh@123.com>
year: 2024
license: MIT
useViper: true
再次使用 init
命令初始化程序:
cobra-cli init
Using config file: /Users/jianghushinian/.cobra.yaml
会提示使用了 ~/.cobra.yaml
配置文件。
现在 LICENSE
文件内容格式如下:
The MIT License (MIT)
Copyright © 2024 f1sh <f1sh@123.com>
...
Copyright
头信息也会包含日期、用户名、用户邮箱。
/*
Copyright © 2024 f1sh <f1sh@123.com>
...
*/
如果你不想把配置保存在 ~/.cobra.yaml
中,cobra-cli
还提供了 --config
标志来指定任意目录下的配置文件。
到这里,常用功能就总结的差不多了。cobra是一个功能强大的命令行框架,进一步提高了命令行编写程序的效率。