1
go get github.com/spf13/cobra/cobra

简介

cobra 是一个命令行程序库,可以用来编写命令行程序。同时,它也提供了一个脚手架, 用于生成基于 cobra 的应用程序框架。非常多知名的开源项目使用了 cobra 库构建命令行,如 KubernetesHugoetcd 等等。

特性

cobra 提供非常丰富的功能:

  • 轻松支持子命令,如 app serverapp fetch 等;
  • 完全兼容 POSIX 选项(包括短、长选项);
  • 嵌套子命令;
  • 全局、本地层级选项。可以在多处设置选项,按照一定的顺序取用;
  • 使用脚手架轻松生成程序框架和命令。

首先需要明确 3 个基本概念:

  • 命令(Command):就是需要执行的操作;
  • 参数(Arg):命令的参数,即要操作的对象;
  • 选项(Flag):命令选项可以调整命令的行为。

下面示例中,server 是一个(子)命令,--port 是选项:

1
hugo server --port=1313

下面示例中,clone 是一个(子)命令,URL 是参数,--bare 是选项:

1
git clone URL --bare

quick start

实现一个简单的命令行程序 git,当然这不是真的 git,只是模拟其命令行。最终还是通过 os/exec 库调用外部程序执行真实的 git 命令,返回结果。 所以我们的系统上要安装 git,且 git 在可执行路径中。目前我们只添加一个子命令 version。目录结构如下:

1
2
3
4
5
6
▾ get-started/
▾ cmd/
helper.go
root.go
version.go
main.go

root.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cmd

import (
"errors"

"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command {
Use: "git",
Short: "Git is a distributed version control system.",
Long: `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.`,
Run: func(cmd *cobra.Command, args []string) {
Error(cmd, args, errors.New("unrecognized command"))
},
}

func Execute() {
rootCmd.Execute()
}

version.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

var versionCmd = &cobra.Command {
Use: "version",
Short: "version subcommand show git version info.",

Run: func(cmd *cobra.Command, args []string) {
output, err := ExecuteCommand("git", "version", args...)
if err != nil {
Error(cmd, args, err)
}

fmt.Fprint(os.Stdout, output)
},
}

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

main.go 文件中只是调用命令入口:

1
2
3
4
5
6
7
8
9
package main

import (
"github.com/darjun/go-daily-lib/cobra/get-started/cmd"
)

func main() {
cmd.Execute()
}

为了编码方便,在 helpers.go 中封装了调用外部程序和错误处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cmd

import (
"fmt"
"os"
"os/exec"

"github.com/spf13/cobra"
)

func ExecuteCommand(name string, subname string, args ...string) (string, error) {
args = append([]string{subname}, args...)

cmd := exec.Command(name, args...)
bytes, err := cmd.CombinedOutput()

return string(bytes), err
}

func Error(cmd *cobra.Command, args []string, err error) {
fmt.Fprintf(os.Stderr, "execute %s args:%v error:%v\n", cmd.Name(), args, err)
os.Exit(1)
}

每个 cobra 程序都有一个根命令,可以给它添加任意多个子命令。我们在 version.goinit 函数中将子命令添加到根命令中。

调用子命令:

1
2
./main.exe version
git version 2.19.1.windows.1

未识别的子命令:

1
2
3
$ ./main.exe clone
Error: unknown command "clone" for "git"
Run 'git --help' for usage.

使用 cobra 构建命令行时,程序的目录结构一般比较简单,推荐使用下面这种结构:

1
2
3
4
5
6
7
▾ appName/
▾ cmd/
cmd1.go
cmd2.go
cmd3.go
root.go
main.go

每个命令实现一个文件,所有命令文件存放在 cmd 目录下。外层的 main.go 仅初始化 cobra。