Go 每日一庫之 Cobra:著名的命令行庫
"Golang 之旅" 插圖,來自 Go Gopher 的 Renee French 創作
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 <this-command>' 輸出的詳細描述.
Long string
// Flag 一組特定於此命令的標誌碼.
Flag flag.FlagSet
// CustomFlags 表示了命令將執行自定義的標誌解析
CustomFlags bool
// Commands 列舉可用的命令和 help 主題.
// 順序和 'go help' 輸出一致.
// 注意:通常最好避免使用子命令.
Commands []*Command
}
此接口是僅適用於標準庫的內部包的一部分。在 2014 年 6 月的 Go 1.4 版本中,Russ Cox 提出了 限制使用內部包和命令的建議 [1]。基於此內部包構建命令會帶來錯誤:
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[2] 所解釋的那樣:這個內部界面設計 催生了了 Cobra[3](Steve Franci 在 Google 工作並曾直接參與了 Go 項目。)。
該項目也建立在來自同一作者的 pflag 項目 [4] 之上,提供符合 POSIX 標準。因此,程序包支持短標記和長標記,如 -e
替代 --example
, 或者多個選項,如 -abc
和 -a
,-b
和 -c
都是是有效選項。這旨在改進 Go 庫中的 flag
包,該庫僅支持標誌 -xxx
。
特性
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[5] 的主要動機之一
輕量
這個庫的代碼主要包含一個文件,而且很好理解,它不會影響你程序的性能。接下來,我們做一個壓力測試(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 的項目 [6] 證實了這一點 - 瞭解 Go 生態系統中有關 CLI 接口的內容總是好的。
讓我們回顧兩個可以替代 Cobra 的項目:
- cli[7],一個用於構建命令行應用程序的包。這個包和 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[8]:雖然託管在 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,該庫基於一個接口而不是一個結構體,因此並使代碼稍顯冗長。
via: https://medium.com/a-journey-with-go/go-thoughts-about-cobra-f4e8c5f18091
作者:Vincent Blanchon[9] 譯者:TomatoAres[10] 校對:DingdingZhou[11]
本文由 GCTT[12] 原創編譯,Go 中文網 [13] 榮譽推出,發佈在 Go 語言中文網公衆號,轉載請聯繫我們授權。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/dRIyZXBaqylwzMxL54S29w