
命令行工具Cobra
https://cloud.tencent.com/developer/article/2403412
1、Cobra简介
Cobra 是一个 Go 语言开发的命令行(CLI)框架,它提供了简洁、灵活且强大的方式来创建命令行程序。它包含一个用于创建命令行程序的库(Cobra 库),以及一个用于快速生成基于 Cobra 库的命令行程序工具(Cobra 命令)。
go get -u github.com/spf13/cobra@latest
cobra的三个元素:command命令、arg参数、flag标志
AppName command arg --flag
🔽 🔽 🔽 🔽
AppName verb noun --adjective
Cobra 的工作原理和内部机制主要包括以下几个关键步骤:
初始化命令:首先,创建一个根命令并初始化 Cobra 库。根命令是整个命令行应用程序的入口,可以包含多个子命令和相关的参数和标志。
定义命令和子命令:使用 Cobra 提供的 API 定义命令和子命令,并为每个命令定义相应的执行函数。
解析命令行参数:当用户在命令行中输入命令时,Cobra 会解析用户输入的命令、参数和标志,并调用相应的执行函数来处理。
执行命令:根据用户输入的命令,Cobra 会调用相应的执行函数来执行命令所需的操作,包括打印输出、调用其他函数等。
处理错误和异常:在执行过程中,Cobra 会检测并处理可能出现的错误和异常情况,以保证命令行应用程序的稳定性和可靠性。
通过这些步骤,Cobra 能够实现一个完整的命令行应用程序,并提供丰富的功能和选项,包括命令、子命令、参数和标志的定义、解析、执行,以及自动生成帮助文档和自动补全等功能。
(1)如何处理命令行参数和标志
Cobra 提供了一种简单而强大的方式来处理命令行参数和标志。可以通过 Cobra 提供的 API 来定义参数和标志,并在执行函数中获取和使用它们。例如:
var cmd = &cobra.Command{
Use: "say",
Short: "Print a message",
Run: func(cmd *cobra.Command, args []string) {
message, _ := cmd.Flags().GetString("message")
fmt.Println(message)
},
}
func init() {
cmd.Flags().StringP("message", "m", "Hello World", "Message to print")
}
在这个例子中,我们定义了一个名为say
的命令,其中包含一个名为message
的标志。在执行函数中,我们通过cmd.Flags().GetString("message")
来获取标志的值,并打印输出。
(2)自动生成帮助文档和自动补全的功能
Cobra 提供了自动生成帮助文档和自动补全的功能,使得用户能够更方便地了解命令行应用程序的使用方式,并提高命令行操作的效率。要启用这些功能,您只需在初始化命令时调用相应的方法即可。例如:
var rootCmd = &cobra.Command{
Use: "app",
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:\n\nCobra is a CLI library for Go that empowers applications.\nThis application is a tool to generate the needed files\nto quickly create a Cobra application.",
Run: func(cmd *cobra.Command, args []string) { },
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.app.yaml)")
rootCmd.AddCommand(addCmd)
rootCmd.AddCommand(deleteCmd)
// Enable auto-completion
rootCmd.GenBashCompletion(os.Stdout)
// Enable automatic generation of help documentation
rootCmd.SetHelpCommand(&cobra.Command{Use: "no-help", Hidden: true})
rootCmd.AddCommand(helpCmd)
}
在这个例子中,我们通过调用 rootCmd.GenBashCompletion(os.Stdout)
方法来启用自动补全功能,并通过 rootCmd.SetHelpCommand()
方法来禁用默认的帮助命令,并使用自定义的帮助命令。这样,用户就可以通过输入命令时按下 Tab
键来自动补全命令和参数,并使用 app help
命令来获取帮助信息了。
2、Cobra使用
我们将创建一个简单的文件管理器命令行应用程序,该应用程序具有以下功能:
列出指定目录下的所有文件和子目录
创建新文件或目录
删除指定的文件或目录
我们首先定义根命令以及三个子命令:list、create 和 delete。根命令用于启动应用程序,而子命令用于执行具体的操作。我们将在各个子命令中添加各种参数和标志,以支持不同的功能。
var rootCmd = &cobra.Command{
Use: "filemgr",
Short: "A simple file manager CLI tool",
Long: "filemgr is a simple file manager CLI tool that allows you to list, create, and delete files and directories.",
}
var listCmd = &cobra.Command{
Use: "list [directory]",
Short: "List all files and directories in the specified directory",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
directory := "."
if len(args) > 0 {
directory = args[0]
}
// 实现列出文件和目录的逻辑
},
}
var createCmd = &cobra.Command{
Use: "create <name>",
Short: "Create a new file or directory with the specified name",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := args[0]
// 实现创建文件或目录的逻辑
},
}
var deleteCmd = &cobra.Command{
Use: "delete <name>",
Short: "Delete the file or directory with the specified name",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := args[0]
// 实现删除文件或目录的逻辑
},
}
func init() {
rootCmd.AddCommand(listCmd)
rootCmd.AddCommand(createCmd)
rootCmd.AddCommand(deleteCmd)
}
在上述子命令中,我们使用了Args
字段来定义子命令所需的参数。此外,我们还可以使用Flags
来定义各种参数和标志,以支持更多的功能。例如,我们可以为list
命令添加一个标志来指定是否显示隐藏文件,为create
命令添加一个标志来指定创建文件还是目录,为delete
命令添加一个标志来指定是否递归删除目录。
var listCmd = &cobra.Command{
Use: "list [directory]",
Short: "List all files and directories in the specified directory",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
directory := "."
if len(args) > 0 {
directory = args[0]
}
showHidden, _ := cmd.Flags().GetBool("hidden")
// 实现列出文件和目录的逻辑
},
}
func init() {
listCmd.Flags().BoolP("hidden", "H", false, "Show hidden files and directories")
rootCmd.AddCommand(listCmd)
}
3、特性和技巧
3.1、处理错误和异常
在命令行应用程序开发中,处理错误和异常情况是至关重要的。Cobra 提供了一种简单而有效的方式来处理错误和异常情况,使得我们能够及时捕获和处理可能出现的错误。以下是一些处理错误和异常情况的常用技巧:
错误处理:在执行命令时,可以通过返回错误对象来指示执行过程中是否发生了错误。在执行函数中,可以使用fmt.Errorf()
函数或errors.New()
函数来创建错误对象,并返回给 Cobra。例如:
Run: func(cmd *cobra.Command, args []string) error {
if err := doSomething(); err != nil {
return fmt.Errorf("error occurred: %v", err)
}
return nil
},
异常处理:除了错误处理外,您还可以使用 Go 语言的panic
和recover
机制来处理更严重的异常情况。在执行函数中,您可以使用panic()
函数来触发一个异常,然后在调用该函数的上层函数中使用recover()
函数来捕获并处理该异常。例如:
Run: func(cmd *cobra.Command, args []string) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
// 执行可能触发 panic 的代码
panic("something went wrong")
},
3.2、自定义函数和选项
除了内置的命令和选项外,Cobra 还允许我们定义自定义函数和选项,以满足特定需求。例如,您可以定义一个自定义函数来实现某个特定功能,并在需要时在命令中调用该函数。您还可以定义自定义选项来扩展 Cobra 的功能,以支持更多的配置选项。以下是一些示例:
// 自定义函数
func customFunction() {
// 实现自定义功能
}
var customCmd = &cobra.Command{
Use: "custom",
Short: "Execute custom function",
Run: func(cmd *cobra.Command, args []string) {
customFunction()
},
}
// 自定义选项
var customOption string
func init() {
rootCmd.PersistentFlags().StringVar(&customOption, "custom-option", "", "A custom option")
}
3.3、PreRun and PostRun Hooks
可以在命令的 main 函数之前或之后运行函数。
PersistentPreRun
PreRun
Run
PostRun
PersistentPostRun
package main
import (
"fmt"
"github.com/spf13/cobra"
)
func main() {
var rootCmd = &cobra.Command{
Use: "root [sub]",
Short: "My root command",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
},
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd Run with args: %v\n", args)
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
},
}
var subCmd = &cobra.Command{
Use: "sub [no options!]",
Short: "My subcommand",
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside subCmd PreRun with args: %v\n", args)
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside subCmd Run with args: %v\n", args)
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside subCmd PostRun with args: %v\n", args)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args)
},
}
rootCmd.AddCommand(subCmd)
rootCmd.SetArgs([]string{""})
rootCmd.Execute()
fmt.Println()
rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
rootCmd.Execute()
}
Inside rootCmd PersistentPreRun with args: []
Inside rootCmd PreRun with args: []
Inside rootCmd Run with args: []
Inside rootCmd PostRun with args: []
Inside rootCmd PersistentPostRun with args: []
Inside rootCmd PersistentPreRun with args: [arg1 arg2]
Inside subCmd PreRun with args: [arg1 arg2]
Inside subCmd Run with args: [arg1 arg2]
Inside subCmd PostRun with args: [arg1 arg2]
Inside subCmd PersistentPostRun with args: [arg1 arg2]
4、简单实例
4.1、文件管理器
我们将创建一个简单的文件管理器命令行应用程序,该应用程序具有以下功能:
列出指定目录下的所有文件和子目录
创建新文件或目录
删除指定的文件或目录
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "file-manager",
Short: "A simple file manager CLI",
Long: `This is a simple command-line tool to manage files and directories.`,
}
var listCmd = &cobra.Command{
Use: "list [dir]",
Short: "List files and directories",
Long: `List all files and directories in the specified directory. Optionally include hidden files.`,
Args: cobra.ExactArgs(1), // Ensure that exactly one argument (directory) is provided
Run: func(cmd *cobra.Command, args []string) {
dir := args[0]
includeHidden, _ := cmd.Flags().GetBool("hidden")
listFiles(dir, includeHidden)
},
}
var createCmd = &cobra.Command{
Use: "create [name]",
Short: "Create a new file or directory",
Long: `Create a new file or directory. Use the --dir flag to create a directory, or create a file by default.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := args[0]
isDir, _ := cmd.Flags().GetBool("dir")
createFileOrDir(name, isDir)
},
}
var deleteCmd = &cobra.Command{
Use: "delete [name]",
Short: "Delete a file or directory",
Long: `Delete the specified file or directory. Use the --recursive flag to delete a directory recursively.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := args[0]
recursive, _ := cmd.Flags().GetBool("recursive")
deleteFileOrDir(name, recursive)
},
}
func init() {
// 也不是所有的flag都可以解析,需要输入提前定义好的flag才可以进行下一步
listCmd.Flags().BoolP("hidden", "h", false, "Include hidden files")
createCmd.Flags().BoolP("dir", "d", false, "Create a directory instead of a file")
deleteCmd.Flags().BoolP("recursive", "r", false, "Recursively delete directories")
// Add subcommands to the root command
rootCmd.AddCommand(listCmd)
rootCmd.AddCommand(createCmd)
rootCmd.AddCommand(deleteCmd)
}
func listFiles(dir string, includeHidden bool) {
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip hidden files unless the flag is set
if !includeHidden && info.Name()[0] == '.' {
return nil
}
fmt.Println(path)
return nil
})
if err != nil {
fmt.Println("Error listing files:", err)
}
}
func createFileOrDir(name string, isDir bool) {
var err error
if isDir {
err = os.Mkdir(name, 0755)
} else {
_, err = os.Create(name)
}
if err != nil {
fmt.Println("Error creating file or directory:", err)
} else {
fmt.Println("Created:", name)
}
}
func deleteFileOrDir(name string, recursive bool) {
var err error
if recursive {
err = os.RemoveAll(name) // Removes directories and their contents recursively
} else {
err = os.Remove(name) // Removes files or empty directories
}
if err != nil {
fmt.Println("Error deleting file or directory:", err)
} else {
fmt.Println("Deleted:", name)
}
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
4.2、docker save
func NewImageCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "image",
Short: "Manage images",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
}
cmd.AddCommand(
NewBuildCommand(dockerCli),
NewHistoryCommand(dockerCli),
NewImportCommand(dockerCli),
NewLoadCommand(dockerCli),
NewPullCommand(dockerCli),
NewPushCommand(dockerCli),
NewSaveCommand(dockerCli),
NewTagCommand(dockerCli),
newListCommand(dockerCli),
newRemoveCommand(dockerCli),
newInspectCommand(dockerCli),
NewPruneCommand(dockerCli),
)
return cmd
}
// NewSaveCommand creates a new `docker save` command
func NewSaveCommand(dockerCli command.Cli) *cobra.Command {
var opts saveOptions
cmd := &cobra.Command{
Use: "save [OPTIONS] IMAGE [IMAGE...]",
Short: "Save one or more images to a tar archive (streamed to STDOUT by default)",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.images = args
return RunSave(cmd.Context(), dockerCli, opts)
},
Annotations: map[string]string{
"aliases": "docker image save, docker save",
},
ValidArgsFunction: completion.ImageNames(dockerCli),
}
flags := cmd.Flags()
flags.StringVarP(&opts.output, "output", "o", "", "Write to a file, instead of STDOUT")
flags.StringVar(&opts.platform, "platform", "", `Save only the given platform variant. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`)
_ = flags.SetAnnotation("platform", "version", []string{"1.48"})
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
return cmd
}
// RunSave performs a save against the engine based on the specified options
func RunSave(ctx context.Context, dockerCli command.Cli, opts saveOptions) error {
if opts.output == "" && dockerCli.Out().IsTerminal() {
return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect")
}
if err := command.ValidateOutputPath(opts.output); err != nil {
return errors.Wrap(err, "failed to save image")
}
var options image.SaveOptions
if opts.platform != "" {
p, err := platforms.Parse(opts.platform)
if err != nil {
return errors.Wrap(err, "invalid platform")
}
// TODO(thaJeztah): change flag-type to support multiple platforms.
options.Platforms = append(options.Platforms, p)
}
responseBody, err := dockerCli.Client().ImageSave(ctx, opts.images, options)
if err != nil {
return err
}
defer responseBody.Close()
if opts.output == "" {
_, err := io.Copy(dockerCli.Out(), responseBody)
return err
}
return command.CopyToFile(opts.output, responseBody)
}