打工还是要打工的。。我最后也没发出去。
紧急处理以后,现在写复盘,大家随我看看我到底是在学习哪些内容。
(以上内容纯属虚构,如有雷同纯属巧合)
简介
之前我们讲过pflag和os.Args,现在说说cobra
这个命令行框架。
Kubernetes
、Hugo
、etcd
这些知名项目都用cobra
来做命令行程序。学起来!
关于作者spf13
,这里多说两句。spf13
开源不少项目,而且他的开源项目质量都比较高。相信使用过 vim
的都知道spf13-vim
,号称 vim 终极配置。可以一键配置,对于我这样的懒人来说绝对是福音。
还有他的viper
是一个完整的配置解决方案。完美支持 JSON/TOML/YAML/HCL/envfile/Java properties
配置文件等格式,还有一些比较实用的特性,如配置热更新、多查找目录、配置保存等。还有非常火的静态网站生成器hugo
也是他的作品牛人就是牛人。
这个牛人 https://github.com/spf13
快速使用
第三方库都需要先安装,后使用。下面命令安装了cobra
生成器程序和 cobra 库:
$ go get github.com/spf13/cobra/cobra
PS: 如果出现了golang.org/x/text
库找不到之类的错误,需要手动从 GitHub 上下载该库,再执行上面的安装命令。
现在要举的例子是让我们的程序调子命令时会透传到git
上,用git version
举例。目录结构如下(手动建的):
get-started/
cmd/
root.go
version.go
utils/
helper.go
main.go
cmd
目标是子命令列表,这里有一个version
命令。root.go
先卖个关子,大家不要理他。main.go
是主程序。helper
是这里使用到的工具类。go.mod
文件我省略了。
下面的代码文件我就省略import "github.com/spf13/cobra"
了,大家知道就行,version.go
文件:
var versionCmd = &cobra.Command{
Use: "version",
Short: "version subcommand show git version info.",
Run: func(cmd *cobra.Command, args []string) {
output, err := utils.ExecuteCommand("git", "version", args...)
if err != nil {
utils.Error(cmd, args, err)
}
fmt.Fprint(os.Stdout, output)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}
- 几个参数含义是子命令名称、子命令短提示、子命令调用的方法
init()
里把子命令加到主命令中去。
你会有疑惑rootCmd
是哪来的吗?实际上我们需要一个根节点,把其他命令加进来。如下是root.go
文件。
var rootCmd = &cobra.Command {
Use: "git",
Short: "Git is a distributed version control system.",
Long: `Git is a free ...省略`,
Run: func(cmd *cobra.Command, args []string) {
utils.Error(cmd, args, errors.New("unrecognized command"))
},
}
func Execute() {
rootCmd.Execute()
}
有没有发现这里不是init()
而是Execute()
?这里此包唯一暴露的公开函数内容,专门供命令初始化使用。如下main.go
文件中的调用命令入口:
import "cmd"
func main() {
cmd.Execute()
}
最后为了编码方便,在helpers.go
中封装了调用外部程序和错误处理函数,我就不展开写了,有兴趣去看我的源码。
https://github.com/golang-min...
cobra
自动生成的帮助信息,very cool
:
$ go run . -h
Git is a free and open source distributed version control system
designed to handle everything from small to very large projects
with speed and efficiency.
Usage:
git [flags]
git [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
version version subcommand show git version info.
Flags:
-h, --help help for git
Use "git [command] --help" for more information about a command.
单个子命令的帮助信息:
$ go run . version -h
version subcommand show git version info.
Usage:
git version [flags]
Flags:
-h, --help help for version
调用子命令:
$ go run . version
git version 2.33.0
未识别的子命令:
$ go run . xxx
Error: unknown command "xxx" for "git"
Run 'git --help' for usage.
使用 cobra
构建命令行时,程序的目录结构一般比较简单,推荐使用下面这种结构:
appName/
cmd/
cmd1.go
cmd2.go
cmd3.go
root.go
main.go
每个命令实现一个文件,所有命令文件存放在cmd
目录下。外层的main.go
仅初始化 cobra。
特性
cobra 提供非常丰富的功能:
- 轻松支持子命令,如
app server
,app fetch
等; - 完全兼容 POSIX 选项(包括短、长选项);
- 嵌套子命令;
- 全局、本地层级选项。可以在多处设置选项,按照一定的顺序取用;
- 使用脚手架轻松生成程序框架和命令。
首先需要明确 3 个基本概念:
- 命令(Command):就是需要执行的操作;
- 参数(Arg):命令的参数,即要操作的对象;
- 选项(Flag):命令选项可以调整命令的行为。
比如
git clone URL --bare
clone
是一个(子)命令,URL
是参数,--bare
是选项。子命令我们已经讲过了,现在讲讲参数。
参数
比如定义命令的地方。
var cloneCmd = &cobra.Command{
Use: "clone url [destination]",
...
Run: func(cmd *cobra.Command, args []string) {
...
会改变帮助函数输出的内容。实际上还是传入字符串数组。
go run . clone -h
Clone a repository into a new directory
Usage:
git clone url [destination] [flags]
Flags:
-h, --help help for clone
选项
cobra
中选项分为两种.
- 一种是永久选项(
PersistentFlags
翻译不太标准,暂时就说永久选项),定义它的命令和其子命令都可以使用。方法是给根命令添加一个选项定义全局选项。 - 另一种是本地选项,只能在定义它的命令中使用。
cobra
使用pflag
解析命令行选项,上次讲过,实际上用法都是一样的。
设置永久选项,在root.go
根命令文件中的init()
函数:
var(
Verbose bool
)
func init() {
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
}
设置本地选项,在子命令的init()
函数:
var(
Source bool
)
func init() {
localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
rootCmd.AddCommand(divideCmd)
}
两种参数都是相同的,长选项/短选项名、默认值和帮助信息。
脚手架
通过前面的介绍,我们也看到了其实 cobra
命令的框架还是比较固定的。这就有了工具的用武之地了,可极大地提高我们的开发效率。
在你的go root
下安装cobra-cli
,确保bin
目录已经放到系统的path
里,之前写的文章-运行那一节有提到过怎么操作,不记得的回去看看哈。
go install github.com/spf13/cobra-cli@latest
下面我们介绍如何使用这个生成器,先看命令帮助:
Usage:
cobra-cli init [path] [flags]
Aliases:
init, initialize, initialise, create
Flags:
-h, --help help for init
Global Flags:
-a, --author string author name for copyright attribution (default "YOUR NAME")
--config string config file (default is $HOME/.cobra.yaml)
-l, --license string name of license for the project
--viper use Viper for configuration
- 根据提示子命令
init
,可选参数为path
。 - 选项为
-a
指定作者,--config string
指定cobra-cli
自己的配置文件 -l
指定license
,--viper
使用viper
来读取配置文件。
使用cobra init
命令创建一个 cobra 应用程序:
$ mkdir appname
$ cd appname
$ cobra-cli init
Error: Please run `go mod init ` before `cobra-cli init`
$ go mod init
go: creating new go.mod: module github.com/golang-minibear2333/cmd_utils/git/appname
$ cobra-cli init
Your Cobra application is ready at
/Users/xxxx/Documents/code/go/src/github.com/golang-minibear2333/cmd_utils/git/appname
- 先初始化
mod
再初始化项目 - 其中
appname
为应用程序名。生成的程序目录结构如下:
.
├── LICENSE
├── cmd
│ └── root.go
├── go.mod
├── go.sum
└── main.go
这个项目结构与之前介绍的完全相同,也是 cobra
推荐使用的结构。同样地,main.go
也仅仅是入口。里面的英文注释非常的清晰,我一下子就看懂了用法,你也试试。
配置读取
除了命令行以外,这个库还可以用来配置读取,我们先创建项目和配置文件:
mkdir cfg_load && cd_cg_load
mkdir config && touch config/cfg.yaml
cat >config/cfg.yaml <<-EOF
people:
name: minibear2333
age: 18
EOF
PS: linux
命令不熟的可以在Go
群里问我。
现在我们尝试读取这个配置文件,直接使用命令来创建读取配置文件的代码。
$ cobra-cli init --viper
Your Cobra application is ready at
/Users/xxx/Documents/code/go/src/github.com/golang-minibear2333/cmd_utils/git/cfg_load
现在就创建了一个默认配置文件为$HOME/.cfg_load.yaml
的命令行程序,而我们之前放在了另一个位置,所以启动的时候需要指定一下。
$ go run . --config==config/cfg.yaml
配置文件就成功载入了,现在你就可以用viper
在需要的地方读取配置了。
为了展示一下配置是否成功读取,继续用cobra-cli
来创建一个子命令。
$ cobra-cli add viperall
修改此子命令Run
函数的内容为
var viperallCmd = &cobra.Command{
Use: "viperall",
Short: "Show cfg all",
Long: `Show the contents of the entire configuration file`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(viper.AllSettings())
},
}
运行,妥了。
$ go run . viperall --config=config/cfg.yaml
Using config file: config/cfg.yaml
map[people:map[age:18 name:minibear2333]]
每次都要指定肯定很麻烦,你熟悉viper
的话可以自己改一下默认文件,把我的项目下下来给我提交一个pr
吧~!
子命令也可以嵌套,只需要在init()
的时候,加到父命令里,当然也可以自动生成。
$ cobra-cli add tt -p viperallCmd
tt created at /Users/xxx/Documents/code/go/src/github.com/golang-minibear2333/cmd_utils/git/cfg_load
$ go run . viperall tt --config=config/cfg.yaml
Using config file: config/cfg.yaml
tt called
- 注意父命令是
viperall
,但是-p
指定的时候要改为viperallCmd
,因为如下(我觉得这个是个很好的贡献pr,你可以建议作者改一下):
var viperallCmd = &cobra.Command{
小结
- 每个 cobra 程序都有一个根命令,可以给它添加任意多个子命令。比如我们在
version.go
的init
函数中将子命令添加到根命令中。 - 创建子命令时指定子命令名称、子命令短提示、子命令调用的方法。
- 三个重要概念,子命令、参数、选项。
- 全局选项和子命令自己使用的选项。
cobra-cli
自动创建项目,自动创建配置文件读取项目,自动增加子命令,自动增加嵌套子命令。
推荐目录结构
.
├── LICENSE
├── cmd
│ ├── root.go
| ├── cmd1.go
├── go.mod
├── go.sum
└── main.go
还有更多! cobra
提供了非常丰富的特性和定制化接口,例如:
- 设置钩子函数,在命令执行前、后执行某些操作。
- 生成 Markdown/ReStructed Text/Man Page 格式的文档。
- 等。自己下来学咯。
cobra
库的使用非常广泛,很多知名项目都有用到,前面也提到过这些项目。学习这些项目是如何使用 cobra
的,可以从中学习 cobra
的特性和最佳实践。这也是学习开源项目的一个很好的途径。
本文由mdnice多平台发布