Cobra 是 Golang 中最流行的命令行应用构建库,被 Kubernetes、Docker、Hugo 等知名项目采用。本教程将带你从零开始构建一个功能完整的CLI应用。
目录
Cobra 简介
Cobra 是一个用于构建CLI应用的强大库,提供以下功能:
- 子命令结构(如git的add、commit、push)
- 支持短选项和长选项(-h, --help)
- 自动生成帮助文档
- 命令行参数验证
- 自定义帮助模板
- 支持配置文件
- 版本信息管理
安装
bash
# 安装Cobra库和CLI工具
go install github.com/spf13/cobra@latest
go install github.com/spf13/cobra-cli@latest
# 在你的项目中添加依赖
go get github.com/spf13/cobra
注意 :从Go 1.17开始,推荐使用go install安装可执行工具,使用go get添加项目依赖。
基本概念
Cobra应用基于三个核心概念:
- Commands(命令):表示一个动作,如"创建文件"
- Arguments(参数):非选项参数,如文件名
- Flags(标志):选项参数,如--verbose, -v
一个好的CLI应用遵循:APPNAME COMMAND ARG --FLAG 模式,例如:
git add README.md --verbosedocker run nginx --port 8080
创建第一个应用
首先创建一个基本的应用:
bash
mkdir mycli
cd mycli
go mod init mycli
go mod tidy
创建 main.go:
go
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
func main() {
var verbose bool
var rootCmd = &cobra.Command{
Use: "mycli",
Short: "一个简单的CLI示例应用",
Long: `这是一个使用Cobra构建的CLI应用示例`,
Run: func(cmd *cobra.Command, args []string) {
if verbose {
fmt.Println("Verbose mode is enabled")
}
fmt.Println("Hello from mycli!")
},
}
rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "enable verbose mode")
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
测试运行:
bash
go run main.go
添加子命令
创建一个更复杂的应用,支持文件操作:
go
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
func main() {
var rootCmd = &cobra.Command{
Use: "filemanager",
Short: "文件管理工具",
Long: `一个简单的文件管理工具,支持查看、创建和删除文件`,
Run: func(cmd *cobra.Command, args []string) {
cmd.Usage()
},
}
rootCmd.AddCommand(createCmd())
rootCmd.AddCommand(listCmd())
rootCmd.AddCommand(deleteCmd())
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
func createCmd() *cobra.Command {
var content string
cmd := &cobra.Command{
Use: "create",
Short: "创建文件",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
filename := args[0]
err := os.WriteFile(filename, []byte(content), 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating file: %v\n", err)
return
}
fmt.Printf("Created file: %s\n", filename)
},
}
cmd.Flags().StringVarP(&content, "content", "c", "", "file content")
return cmd
}
func listCmd() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "列出当前目录文件",
Run: func(cmd *cobra.Command, args []string) {
files, err := os.ReadDir(".")
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading directory: %v\n", err)
return
}
fmt.Println("Current directory files:")
for _, file := range files {
fmt.Printf(" %s\n", file.Name())
}
},
}
}
func deleteCmd() *cobra.Command {
var force bool
cmd := &cobra.Command{
Use: "delete",
Short: "删除文件",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
filename := args[0]
// 检查文件是否存在
if _, err := os.Stat(filename); os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Error: file '%s' does not exist\n", filename)
return
}
// 强制删除或确认删除
if !force {
fmt.Printf("Are you sure you want to delete '%s'? (y/N): ", filename)
var response string
_, err := fmt.Scanln(&response)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
return
}
if response != "y" && response != "Y" {
fmt.Println("Cancelled")
return
}
}
err := os.Remove(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "Error deleting file: %v\n", err)
return
}
fmt.Printf("Deleted file: %s\n", filename)
},
}
cmd.Flags().BoolVarP(&force, "force", "f", false, "force deletion without confirmation")
return cmd
}
现在测试这个应用:
bash
# 查看帮助
go run main.go --help
# 创建文件
go run main.go create test.txt --content "Hello World"
# 列出文件
go run main.go list
# 删除文件
go run main.go delete test.txt
# 强制删除文件
go run main.go create temp.txt --content "Temporary"
go run main.go delete temp.txt --force
处理Flags
Cobra支持多种类型的Flags:
布尔型Flags
go
var verbose bool
rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
整型Flags
go
var port int
rootCmd.Flags().IntVarP(&port, "port", "p", 8080, "server port")
字符串Flags
go
var configFile string
rootCmd.Flags().StringVarP(&configFile, "config", "c", "", "config file path")
字符串Slice Flags
go
var tags []string
rootCmd.Flags().StringSliceVarP(&tags, "tags", "t", []string{}, "tags to add")
持续时间Flags
go
var timeout time.Duration
rootCmd.Flags().DurationVarP(&timeout, "timeout", "t", 30*time.Second, "timeout duration")
参数验证
Cobra提供多种参数验证方式:
go
func exactArgsCommand() *cobra.Command {
return &cobra.Command{
Use: "exact-args <file1> <file2>",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Received exactly 2 arguments:", args)
},
}
}
func rangeArgsCommand() *cobra.Command {
return &cobra.Command{
Use: "range-args <files...>",
Args: cobra.RangeArgs(1, 5),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Received 1-5 arguments:", args)
},
}
}
func customValidatorCommand() *cobra.Command {
return &cobra.Command{
Use: "custom <username>",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("exactly one username required")
}
username := args[0]
if len(username) < 3 {
return fmt.Errorf("username must be at least 3 characters long")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Username is valid:", args[0])
},
}
}
帮助系统
Cobra自动生成帮助文档,你也可以自定义:
go
var rootCmd = &cobra.Command{
Use: "app",
Short: "Application",
Long: `Application description
This is a detailed description of the application
that can span multiple lines.`,
// 自定义帮助函数
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
cmd.Usage()
return
}
// 应用逻辑
},
}
// 自定义帮助模板
rootCmd.SetHelpTemplate(`{{.Name}} - {{.UsageString}}`)
// 自定义用法模板
rootCmd.SetUsageTemplate(`Usage: {{.UsageString}}`)
帮助示例
bash
# 查看根命令帮助
go run main.go --help
# 查看子命令帮助
go run main.go create --help
高级特性
PersistentFlags vs LocalFlags
- PersistentFlags: 可用于当前命令及其所有子命令
- LocalFlags: 仅可用于当前命令
go
// 全局verbose flag,所有子命令都可以使用
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
// 仅当前命令可用的local flag
rootCmd.Flags().BoolVarP(&localFlag, "local", "l", false, "local flag only")
组织大型项目的命令结构
对于大型项目,建议按功能模块组织命令文件:
myapp/
├── cmd/
│ ├── root.go # 根命令
│ ├── serve.go # serve子命令
│ ├── config.go # config子命令
│ └── version.go # version子命令
├── main.go # 入口文件
└── go.mod
配置文件集成
Cobra可以与Viper结合使用,支持配置文件:
go
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file")
viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
viper.AddConfigPath(".")
viper.SetConfigName(".myapp")
}
viper.AutomaticEnv()
viper.ReadInConfig()
}
测试Cobra命令
go
func TestRootCommand(t *testing.T) {
cmd := &cobra.Command{Use: "test"}
buf := new(bytes.Buffer)
cmd.SetOut(buf)
err := cmd.Execute()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
output := buf.String()
if output != expected {
t.Errorf("Expected %q, got %q", expected, output)
}
}
自定义帮助模板
go
const customHelp = `Usage: {{.CommandPath}} [command]
{{.Short}}
Commands:
{{range .Commands}}{{if not .Hidden}}
{{.NamePadding}}{{.Name}} {{.Short}}{{end}}{{end}}
Run "{{.CommandPath}} [command] --help" for more information.
`
rootCmd.SetHelpTemplate(customHelp)
最佳实践
- 命令命名 :使用小写、短横线分隔(如
my-command) - 错误处理:始终检查错误并返回适当的退出码
- 文档:为每个命令提供清晰的Short和Long描述
- 测试:为复杂逻辑编写单元测试
- 版本管理 :使用
version命令显示版本信息
实际案例:文件管理工具
让我们创建一个更完整、更实用的文件管理工具:
go
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/spf13/cobra"
)
type FileManager struct {
verbose bool
recursive bool
}
func main() {
fm := &FileManager{}
var rootCmd = &cobra.Command{
Use: "fman",
Short: "强大的文件管理工具",
Long: `文件管理工具,支持查找、复制、移动、删除等操作`,
Run: func(cmd *cobra.Command, args []string) {
cmd.Usage()
},
}
// 全局Flags
rootCmd.PersistentFlags().BoolVarP(&fm.verbose, "verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().BoolVarP(&fm.recursive, "recursive", "r", false, "recursive operation")
// 添加子命令
rootCmd.AddCommand(
findCmd(fm),
copyCmd(fm),
moveCmd(fm),
removeCmd(fm),
infoCmd(fm),
versionCmd(),
)
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
func findCmd(fm *FileManager) *cobra.Command {
var name, pattern string
cmd := &cobra.Command{
Use: "find <path>",
Short: "查找文件",
Args: cobra.RangeArgs(0, 1),
Run: func(cmd *cobra.Command, args []string) {
path := "."
if len(args) > 0 {
path = args[0]
}
var findFunc func(string) error
findFunc = func(dir string) error {
entries, err := os.ReadDir(dir)
if err != nil {
return err
}
for _, entry := range entries {
fullPath := filepath.Join(dir, entry.Name())
// 名称匹配
if name != "" && strings.Contains(entry.Name(), name) {
if fm.verbose {
fmt.Printf("Found (name match): %s\n", fullPath)
} else {
fmt.Println(fullPath)
}
}
// 模式匹配
if pattern != "" {
match, _ := filepath.Match(pattern, entry.Name())
if match {
if fm.verbose {
fmt.Printf("Found (pattern match): %s\n", fullPath)
} else {
fmt.Println(fullPath)
}
}
}
// 递归搜索目录
if fm.recursive && entry.IsDir() && !strings.Contains(entry.Name(), ".") {
findFunc(fullPath)
}
}
return nil
}
if err := findFunc(path); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
},
}
cmd.Flags().StringVarP(&name, "name", "n", "", "search by name")
cmd.Flags().StringVarP(&pattern, "pattern", "p", "", "search by pattern (e.g., *.txt)")
return cmd
}
func copyCmd(fm *FileManager) *cobra.Command {
return &cobra.Command{
Use: "copy <source> <destination>",
Short: "复制文件或目录",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
src, dst := args[0], args[1]
srcInfo, err := os.Stat(src)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
return
}
if fm.verbose {
fmt.Printf("Copying %s to %s\n", src, dst)
}
if srcInfo.IsDir() {
copyDirectory(src, dst, fm.verbose)
} else {
copyFile(src, dst, fm.verbose)
}
},
}
}
func copyFile(src, dst string, verbose bool) error {
data, err := os.ReadFile(src)
if err != nil {
return err
}
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
err = os.WriteFile(dst, data, srcInfo.Mode())
if err != nil {
return err
}
if verbose {
fmt.Printf("Copied file: %s -> %s\n", src, dst)
}
return nil
}
func copyDirectory(src, dst string, verbose bool) error {
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
err = os.MkdirAll(dst, srcInfo.Mode())
if err != nil {
return err
}
entries, err := os.ReadDir(src)
if err != nil {
return err
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
err = copyDirectory(srcPath, dstPath, verbose)
if err != nil {
return err
}
} else {
err = copyFile(srcPath, dstPath, verbose)
if err != nil {
return err
}
}
}
if verbose {
fmt.Printf("Copied directory: %s -> %s\n", src, dst)
}
return nil
}
func moveCmd(fm *FileManager) *cobra.Command {
return &cobra.Command{
Use: "move <source> <destination>",
Short: "移动文件或目录",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
src, dst := args[0], args[1]
if fm.verbose {
fmt.Printf("Moving %s to %s\n", src, dst)
}
err := os.Rename(src, dst)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
return
}
if fm.verbose {
fmt.Printf("Moved: %s -> %s\n", src, dst)
}
},
}
}
func removeCmd(fm *FileManager) *cobra.Command {
var force bool
cmd := &cobra.Command{
Use: "remove <path>",
Short: "删除文件或目录",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
path := args[0]
// 检查是否存在
if _, err := os.Stat(path); os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Error: %s does not exist\n", path)
return
}
// 确认删除
if !force {
fmt.Printf("Are you sure you want to delete '%s'? (y/N): ", path)
var response string
_, err := fmt.Scanln(&response)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
return
}
if response != "y" && response != "Y" {
fmt.Println("Cancelled")
return
}
}
if fm.verbose {
fmt.Printf("Removing %s\n", path)
}
err := os.RemoveAll(path)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
return
}
if fm.verbose {
fmt.Printf("Removed: %s\n", path)
}
},
}
cmd.Flags().BoolVarP(&force, "force", "f", false, "force deletion without confirmation")
return cmd
}
func infoCmd(fm *FileManager) *cobra.Command {
return &cobra.Command{
Use: "info <path>",
Short: "显示文件信息",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string] {
path := args[0]
info, err := os.Stat(path)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
return
}
fmt.Printf("Path: %s\n", path)
fmt.Printf("Size: %d bytes\n", info.Size())
fmt.Printf("Mode: %s\n", info.Mode())
fmt.Printf("Modified: %s\n", info.ModTime().Format(time.RFC3339))
if info.IsDir() {
fmt.Printf("Type: Directory\n")
} else {
fmt.Printf("Type: File\n")
}
},
}
}
func versionCmd() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "显示版本信息",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("File Manager CLI v1.0.0")
},
}
}
总结
通过本教程,你学会了:
- Cobra基础:安装、基本概念、创建第一个应用
- 子命令:添加和管理多个子命令
- Flags处理:各种类型的Flags使用方法
- 参数验证:确保输入参数的有效性
- 帮助系统:自动生成和自定义帮助文档
- 实际应用:构建了一个功能完整的文件管理工具
Cobra是构建现代CLI应用的强大工具,掌握它能让你快速构建专业级的命令行应用。记住实践是学习的最好方法,多写代码,多实验,你很快就能熟练掌握Cobra!