# Cobra - Go 命令行框架完整教程
# Cobra 簡介
Cobra 是一個用于構建現代化 Go 命令行應用的強大框架。
# 核心特性
- 子命令支持:輕松構建複雜的多層命令結構(如
docker container ls ) - 自動幫助文本:自動生成
--help 和 --version - 標誌解析:支持長標誌(
--flag )、短標誌( -f )、持久標誌等 - 命令行完成:自動生成 bash/zsh 完成代碼
- 手冊頁生成:生成 Unix man 頁面
- 龐大生態:Docker、kubectl、Hugo 等知名項目都使用 Cobra
# 為什麼選擇 Cobra?
| 對比 | Cobra | flag | urfave/cli |
|---|
| 子命令支持 | ⭐⭐⭐⭐⭐ | 無 | ⭐⭐⭐⭐ |
| 代碼組織性 | ⭐⭐⭐⭐⭐ | 差 | ⭐⭐⭐ |
| 自動完成 | ⭐⭐⭐⭐ | 無 | ⭐⭐ |
| 學習曲線 | 中等 | 低 | 低 |
| 社區活躍度 | ⭐⭐⭐⭐⭐ | N/A | ⭐⭐⭐ |
# 安裝和項目初始化
# 安裝 Cobra
| |
| go get -u github.com/spf13/cobra/v2 |
| |
| |
| go install github.com/spf13/cobra-cli@latest |
# 使用 Cobra CLI 生成項目骨架
| |
| mkdir myapp && cd myapp |
| go mod init github.com/username/myapp |
| |
| |
| cobra-cli init |
| |
| |
| myapp/ |
| ├── cmd/ |
| │ └── root.go |
| ├── main.go |
| └── go.mod |
# 手動添加子命令
| |
| cobra-cli add serve |
| cobra-cli add config |
| cobra-cli add config get |
| cobra-cli add config set |
| |
| |
| cmd/ |
| ├── root.go |
| ├── serve.go |
| ├── config.go |
| ├── configGet.go |
| └── configSet.go |
# 核心概念
# 1. Command 結構
| type Command struct { |
| Use: string |
| Short: string |
| Long: string |
| Run: RunE |
| Args: PositionalArgs |
| } |
# 2. 標誌(Flags)vs 位置參數(Arguments)
| |
| |
| myCmd.Flags().StringVar(&name, "name", "Alice", "用戶名稱") |
| myCmd.Flags().IntVar(&age, "age", 0, "用戶年齡") |
| |
| |
| |
| myCmd.Args = cobra.ExactArgs(2) |
| myCmd.Run = func(cmd *cobra.Command, args []string) { |
| name := args[0] |
| age := args[1] |
| } |
# 3. 標誌類型
| |
| cmd.Flags().StringVar(&str, "name", "default", "description") |
| |
| |
| cmd.Flags().IntVar(&num, "count", 0, "description") |
| |
| |
| cmd.Flags().BoolVar(&enabled, "enable", false, "description") |
| |
| |
| cmd.Flags().StringSliceVar(&tags, "tag", []string{}, "tags") |
| |
| |
| cmd.Flags().StringVarP(&name, "name", "n", "default", "description") |
# 4. 持久標誌(Persistent Flags)
持久標誌定義在父命令上但可被子命令使用:
| package cmd |
| |
| import "github.com/spf13/cobra" |
| |
| var verbose bool |
| |
| var rootCmd = &cobra.Command{ |
| Use: "myapp", |
| Short: "My application", |
| } |
| |
| func init() { |
| |
| rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "verbose output") |
| |
| |
| rootCmd.Flags().BoolVar(&debug, "debug", false, "debug mode") |
| } |
# 實戰:構建文件管理 CLI 工具
# 完整項目結構
filemanager/
├── main.go
├── go.mod
├── go.sum
├── cmd/
│ ├── root.go # 根命令
│ ├── list.go # list 子命令
│ ├── search.go # search 子命令
│ ├── delete.go # delete 子命令
│ └── config.go # config 子命令
└── internal/
└── fileops/
└── operations.go # 文件操作實現
# main.go
| package main |
| |
| import ( |
| "filemanager/cmd" |
| ) |
| |
| func main() { |
| cmd.Execute() |
| } |
# cmd/root.go (根命令)
| package cmd |
| |
| import ( |
| "fmt" |
| "os" |
| |
| "github.com/spf13/cobra" |
| ) |
| |
| var verbose bool |
| |
| var rootCmd = &cobra.Command{ |
| Use: "filemanager", |
| Short: "A simple file management tool", |
| Long: "FileManager 是一個輕量級的文件管理工具,支持列表、搜索、刪除等操作", |
| Run: func(cmd *cobra.Command, args []string) { |
| if len(args) == 0 { |
| cmd.Help() |
| } |
| }, |
| } |
| |
| func init() { |
| |
| rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") |
| } |
| |
| func Execute() { |
| if err := rootCmd.Execute(); err != nil { |
| fmt.Fprintf(os.Stderr, "Error: %v\n", err) |
| os.Exit(1) |
| } |
| } |
# cmd/list.go (列出文件)
| package cmd |
| |
| import ( |
| "fmt" |
| "os" |
| "path/filepath" |
| |
| "github.com/spf13/cobra" |
| ) |
| |
| var listCmd = &cobra.Command{ |
| Use: "list [dir]", |
| Short: "List files in directory", |
| Long: "列出指定目錄中的所有文件和目錄", |
| Args: cobra.MaximumNArgs(1), |
| Aliases: []string{"ls"}, |
| RunE: func(cmd *cobra.Command, args []string) error { |
| dir := "." |
| if len(args) > 0 { |
| dir = args[0] |
| } |
| |
| entries, err := os.ReadDir(dir) |
| if err != nil { |
| return fmt.Errorf("無法讀取目錄: %w", err) |
| } |
| |
| fmt.Printf("目錄: %s\n", dir) |
| fmt.Println("─────────────────────────────") |
| |
| for _, entry := range entries { |
| info, _ := entry.Info() |
| kind := "📄" |
| if entry.IsDir() { |
| kind = "📁" |
| } |
| |
| mode := "" |
| if verbose { |
| mode = info.Mode().String() |
| } |
| |
| fmt.Printf("%s %-30s %s\n", kind, entry.Name(), mode) |
| } |
| |
| return nil |
| }, |
| } |
| |
| func init() { |
| rootCmd.AddCommand(listCmd) |
| } |
# cmd/search.go (搜索文件)
| package cmd |
| |
| import ( |
| "fmt" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "github.com/spf13/cobra" |
| ) |
| |
| var ( |
| searchPattern string |
| searchDir string |
| ) |
| |
| var searchCmd = &cobra.Command{ |
| Use: "search", |
| Short: "Search for files matching pattern", |
| Long: "在指定目錄中遞歸搜索匹配模式的文件", |
| RunE: func(cmd *cobra.Command, args []string) error { |
| if searchPattern == "" { |
| return fmt.Errorf("必須指定搜索模式: --pattern 或 -p") |
| } |
| |
| if searchDir == "" { |
| searchDir = "." |
| } |
| |
| matches := []string{} |
| |
| |
| err := filepath.Walk(searchDir, func(path string, info os.FileInfo, err error) error { |
| if err != nil { |
| return nil |
| } |
| |
| if strings.Contains(info.Name(), searchPattern) { |
| matches = append(matches, path) |
| } |
| return nil |
| }) |
| |
| if err != nil { |
| return err |
| } |
| |
| fmt.Printf("找到 %d 個匹配文件\n", len(matches)) |
| for _, match := range matches { |
| fmt.Println(" " + match) |
| } |
| |
| return nil |
| }, |
| } |
| |
| func init() { |
| rootCmd.AddCommand(searchCmd) |
| |
| |
| searchCmd.Flags().StringVarP(&searchPattern, "pattern", "p", "", "搜索模式") |
| searchCmd.Flags().StringVarP(&searchDir, "dir", "d", ".", "搜索根目錄") |
| |
| |
| searchCmd.MarkFlagRequired("pattern") |
| } |
# cmd/delete.go (刪除文件)
| package cmd |
| |
| import ( |
| "fmt" |
| "os" |
| |
| "github.com/spf13/cobra" |
| ) |
| |
| var ( |
| force bool |
| ) |
| |
| var deleteCmd = &cobra.Command{ |
| Use: "delete <file>", |
| Short: "Delete a file", |
| Long: "刪除指定文件", |
| Args: cobra.ExactArgs(1), |
| RunE: func(cmd *cobra.Command, args []string) error { |
| filePath := args[0] |
| |
| |
| if _, err := os.Stat(filePath); os.IsNotExist(err) { |
| return fmt.Errorf("文件不存在: %s", filePath) |
| } |
| |
| |
| if !force { |
| var response string |
| fmt.Printf("確定要刪除文件 '%s' 嗎? (y/n) ", filePath) |
| fmt.Scanln(&response) |
| |
| if response != "y" && response != "Y" { |
| fmt.Println("已取消刪除") |
| return nil |
| } |
| } |
| |
| |
| if err := os.Remove(filePath); err != nil { |
| return fmt.Errorf("刪除失敗: %w", err) |
| } |
| |
| fmt.Printf("✓ 文件已刪除: %s\n", filePath) |
| return nil |
| }, |
| } |
| |
| func init() { |
| rootCmd.AddCommand(deleteCmd) |
| |
| deleteCmd.Flags().BoolVarP(&force, "force", "f", false, "跳過確認,直接刪除") |
| } |
# 進階功能
# 1. 自定義幫助文本
| cmd.SetHelpTemplate(`用法: {{.UseLine}} |
| |
| {{.Short}} |
| |
| 子命令: |
| {{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} |
| {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}} |
| |
| 標誌: |
| {{.LocalFlags.FlagUsages}} |
| `) |
# 2. 執行前 / 執行後 Hook
| cmd := &cobra.Command{ |
| Use: "serve", |
| PreRun: func(cmd *cobra.Command, args []string) { |
| fmt.Println("準備啟動服務...") |
| }, |
| Run: func(cmd *cobra.Command, args []string) { |
| fmt.Println("服務運行中...") |
| }, |
| PostRun: func(cmd *cobra.Command, args []string) { |
| fmt.Println("服務已停止") |
| }, |
| } |
# 3. 自動完成(Bash/Zsh)
| |
| myapp completion bash | sudo tee /usr/share/bash-completion/completions/myapp |
| myapp completion zsh | sudo tee /etc/zsh/completions/_myapp |
| |
| |
| exec bash |
# 4. Man 頁面生成
| |
| import "github.com/spf13/cobra/doc" |
| |
| func init() { |
| |
| doc.GenManTree(rootCmd, nil, "./man/") |
| } |
# 測試 Cobra 命令
| package cmd |
| |
| import ( |
| "bytes" |
| "testing" |
| |
| "github.com/spf13/cobra" |
| ) |
| |
| func TestListCommand(t *testing.T) { |
| |
| cmd := listCmd |
| |
| |
| output := new(bytes.Buffer) |
| cmd.SetOut(output) |
| cmd.SetErr(output) |
| |
| |
| cmd.SetArgs([]string{"."}) |
| err := cmd.Execute() |
| |
| if err != nil { |
| t.Fatalf("命令執行失敗: %v", err) |
| } |
| |
| |
| if !bytes.Contains(output.Bytes(), []byte("目錄")) { |
| t.Fatal("輸出缺少預期內容") |
| } |
| } |
# 最佳實踐
- 命令組織:按功能分組子命令(如
config get 、 config set ) - 錯誤處理:使用
RunE 而不是 Run ,返回可檢查的錯誤 - 驗證輸入:使用
Args 和 MarkFlagRequired() 驗證參數 - 文檔:充實
Long 字段,提供實際示例 - 測試:為每個命令編寫單元測試
- 版本管理:使用構建標誌注入版本信息
- 配置文件:使用
viper 與 Cobra 集成管理配置
# 與 Viper 集成(配置管理)
| |
| go get github.com/spf13/viper |
| import "github.com/spf13/viper" |
| |
| func init() { |
| cobra.OnInitialize(initConfig) |
| } |
| |
| func initConfig() { |
| viper.SetConfigFile(".filemanager.yaml") |
| if err := viper.ReadInConfig(); err == nil { |
| fmt.Println("使用配置文件:", viper.ConfigFileUsed()) |
| } |
| } |
# 真實項目示例
以下項目都使用 Cobra:
- Docker:
docker container ls 、 docker image build - kubectl:
kubectl get pods 、 kubectl apply -f - Hugo:
hugo new post 、 hugo server - Github CLI:
gh repo clone 、 gh issue create
# 總結
Cobra 提供了構建專業級 CLI 工具所需的全部功能:
- ✅ 直觀的命令結構
- ✅ 靈活的參數解析
- ✅ 自動幫助和完成
- ✅ 活躍的社區和廣泛應用
無論是構建簡單的命令行工具還是複雜的 DevOps 工具,Cobra 都是 Go 開發者的首選框架。
# 參考資源
- Cobra 官方文檔
- Cobra GitHub 倉庫
- Viper - Cobra 配置管理