关于 Go 语言优秀命令行库 Cobra 的探索(关于我妈的一切)

网友投稿 1319 2022-08-19

关于 Go 语言优秀命令行库 Cobra 的探索(关于我妈的一切)

关于 Go 语言优秀命令行库 Cobra 的探索(关于我妈的一切)

Cobra 是 Golang 生态系统中最着名的项目之一。它简单,高效,并得到 Go 社区的大力支持。让我们来深入探索一下。

设计

Cobra 中的 Command 是一个具有名称,使用描述和运行逻辑函数的结构体:

cmd := &cobra.Command{

Run: runGreet,

Use: `greet`,

Short: "Greet",

Long: "This command will print Hello World",

}

设计非常类似于原生的 go 标准库命令,如 go env,go fmt等

比如,go fmt 命令结构:

var CmdFmt = &base.Command{

Run: runFmt,

UsageLine: "go fmt [-n] [-x] [packages]",

Short: "gofmt (reformat) package sources",

Long: `

Fmt runs the command 'gofmt -l -w' on the packages named

by the import paths. It prints the names of the files that are modified.

For more about gofmt, see 'go doc cmd/gofmt'.

For more about specifying packages, see 'go help packages'.

The -n flag prints commands that would be executed.

The -x flag prints commands as they are executed.

To run gofmt with specific options, run gofmt itself.

See also: go fix, go vet.

`,

}

如果你熟悉了 Cobra,很容易理解内部命令是如何工作的,反之亦然。我们可能会想,当 Go 已经定义了命令接口后,为什么还要要使用外部库?

Go 标准库定义的接口:

type Command struct { // Run 运行命令. // 参数在命令之后 Run func(cmd *Command, args []string) // UsageLine 是一行描述信息.

// 其中第一个词语应是命令. UsageLine string // Short 是 'go help' 输出的简单描述. Short string // Long 是 'go help ' 输出的详细描述. Long string // Flag 一组特定于此命令的标志码. Flag flag.FlagSet // CustomFlags 表示了命令将执行自定义的标志解析 CustomFlags bool // Commands 列举可用的命令和 help 主题.

// 顺序和 'go help' 输出一致.

// 注意:通常最好避免使用子命令. Commands []*Command }

此接口是仅适用于标准库的内部包的一部分。在 2014 年 6 月的 Go 1.4 版本中,Russ Cox 提出了 限制使用内部包和命令的建议。基于此内部包构建命令会带来错误:

package main import ( "cmd/go/internal/base" ) func main() {

cmd := &base.Command{

Run: func(cmd *base.Command, args []string) { println(`Hello`)

},

Short: `Hello`,

}

cmd.Run(cmd, []string{})

}

main.go:4:2: use of internal package cmd/go/internal/base not allowed

然而,正如 Cobra 创建者 Steve Francia所解释的那样:这个内部界面设计 催生了了 Cobra(Steve Franci 在 Google 工作并曾直接参与了 Go 项目。)。

特性

Cobra 有一些值得了解的简便方法:

Cobra 提供了两种方法来运行我们的逻辑:Run func(cmd *Command, args []string) 和 RunE func(cmd *Command, args []string) error ,后者可以返回一个错误,我们将能够从 Execute() 方法的返回中捕获。

Command 结构 提供了一个 Aliases(别名) 属性,允许我们将命令迁移到一个新名称,而不需要在alias属性中通过映射旧名称来破坏现有的行为。这种兼容性策略甚至可以通过使用 Deprecated 属性来增强,该属性允许您将一个命令标记为Deprecated(即将弃用,不推荐使用),并在删除它之前提供一个简短的说明。

由于每个命令都可以嵌入其他命令,因此 Cobra 本身支持嵌套命令,并允许我们像下边这样编写:

go run main.go foo bar

在这里, foo 是命令,bar 是嵌套命令:

package main import ( "github.com/spf13/cobra" ) func main() {

cmd := newCommand() // 构建一般命令 cmd.AddCommand(newNestedCommand()) // 加入嵌套命令 rootCmd := &cobra.Command{}

rootCmd.AddCommand(cmd) if err := rootCmd.Execute(); err != nil { println(err.Error())

}

} func newCommand() *cobra.Command {

cmd := &cobra.Command{

Run: func (cmd *cobra.Command, args []string) { println(`Foo`)

},

Use: `foo`,

Short: "Command foo",

Long: "This is a command",

} return cmd

} func newNestedCommand() *cobra.Command {

cmd := &cobra.Command{

Run: func (cmd *cobra.Command, args []string) { println(`Bar`)

},

Use: `bar`,

Short: "Command bar",

Long: "This is a nested command",

} return cmd

}

可以使用嵌套命令是 决定构建 Cobra的主要动机之一

轻量

这个库的代码主要包含一个文件,而且很好理解,它不会影响你程序的性能。接下来,我们做一个压力测试(benchmark):

package main import ( "github.com/spf13/cobra" "math/rand" "os" "strconv" "testing" ) func BenchmarkCmd(b *testing.B) { for i := 0; i < b.N; i++ {

root := &cobra.Command{

Run: func(cmd *cobra.Command, args []string) { println(`main`)

},

Use: `test`,

Short: "test",

}

max := 100 for c := 0; c < max; c++ {

cmd := &cobra.Command{

Run: func(cmd *cobra.Command, args []string) {

_ = c

},

Use: `test-`+strconv.Itoa(c),

Short: `test `+strconv.Itoa(c),

}

root.AddCommand(cmd)

}

r := rand.Intn(max)

os.Args = []string{"go", "test-"+strconv.Itoa(r)}

_ = root.Execute()

}

}

Cobra 运行 50 条命令只有 49.0μs 负载:

name time/op

Cmd-8 49.0µs ± 1%

name alloc/op

Cmd-8 78.3kB ± 0%

name allocs/op

Cmd-8 646 ± 0%

由于 Cobra 被设计运行在 CLI 模式下, 性能并不重要, 但是可以看出这个库有多么轻量.

可替代性

即使 Cobra 倾向于成为 Go 社区的标准包 - 浏览最近使用 Cobra 的项目证实了这一点 - 了解 Go 生态系统中有关 CLI 接口的内容总是好的。

让我们回顾两个可以替代 Cobra 的项目:

cli,一个用于构建命令行应用程序的包。这个包和 Cobra 一样流行,与嵌套命令,bash 补全,hook(钩子),alias(别名)等非常相似。但是,与 Cobra 不同,这个包使用 Go 库中的原生flag包。

urfave/cli 例子:

package main import ( "log" "os" "github.com/urfave/cli" ) func main() {

app := cli.NewApp()

app.Commands = []cli.Command{

{

Action: func(c *cli.Context) error { println("Hello world") return nil },

Name: `greet`,

Usage: "This command will print Hello World",

},

}

err := app.Run(os.Args) if err != nil {

log.Fatal(err)

}

}

subcommands:虽然托管在 Google 的 Github 帐户中,但该项目并非官方 Google 产品。该库也很简单

google/subcommands 示例

package main import ( "context" "flag" "github.com/google/subcommands" ) type GreetCommand struct {} func (g *GreetCommand) Name() string { return "greet" } func (g *GreetCommand) Synopsis() string { return "Greet the world." } func (g *GreetCommand) Usage() string { return `Print Hello World.` } func (g *GreetCommand) SetFlags(*flag.FlagSet) {} func (p *GreetCommand) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { println(`Hello World`) return subcommands.ExitSuccess

} func main() {

subcommands.Register(&GreetCommand{}, "foo")

flag.Parse()

subcommands.Execute(context.Background())

}

如我们之前看到的 Cobra 或 CLI,该库基于一个接口而不是一个结构体,因此并使代码稍显冗长。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Go语言如何实现stop the world?(go语言如何实现串口mark校验)
下一篇:Go监控模式(Monitor Pattern)(go 监控系统)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~