# Cobra - Go 命令行框架完整教程

# Cobra 簡介

Cobra 是一個用于構建現代化 Go 命令行應用的強大框架。

# 核心特性

  • 子命令支持:輕松構建複雜的多層命令結構(如 docker container ls
  • 自動幫助文本:自動生成 --help--version
  • 標誌解析:支持長標誌( --flag )、短標誌( -f )、持久標誌等
  • 命令行完成:自動生成 bash/zsh 完成代碼
  • 手冊頁生成:生成 Unix man 頁面
  • 龐大生態:Docker、kubectl、Hugo 等知名項目都使用 Cobra

# 為什麼選擇 Cobra?

對比Cobraflagurfave/cli
子命令支持⭐⭐⭐⭐⭐⭐⭐⭐⭐
代碼組織性⭐⭐⭐⭐⭐⭐⭐⭐
自動完成⭐⭐⭐⭐⭐⭐
學習曲線中等
社區活躍度⭐⭐⭐⭐⭐N/A⭐⭐⭐

# 安裝和項目初始化

# 安裝 Cobra

# 方法 1:直接安裝
go get -u github.com/spf13/cobra/v2
# 方法 2:使用 cobra-cli 工具(推薦)
go install github.com/spf13/cobra-cli@latest

# 使用 Cobra CLI 生成項目骨架

# 創建項目目錄
mkdir myapp && cd myapp
go mod init github.com/username/myapp
# 使用 cobra-cli 初始化根命令
cobra-cli init
# 自動生成的結構
myapp/
├── cmd/
│   └── root.go           # 根命令定義
├── main.go               # 應用入口
└── go.mod

# 手動添加子命令

# 使用 cobra-cli add 命令快速添加
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      // 簡短描述(在 help 中顯示)
    Long:  string      // 長描述(使用 -h 時顯示)
    Run:   RunE        // 命令邏輯
    Args:  PositionalArgs // 位置參數驗證
}

# 2. 標誌(Flags)vs 位置參數(Arguments)

// 標誌:命名參數,順序無關
// myapp --name "Alice" --age 30
myCmd.Flags().StringVar(&name, "name", "Alice", "用戶名稱")
myCmd.Flags().IntVar(&age, "age", 0, "用戶年齡")
// 位置參數:基於位置的參數,順序重要
// myapp alice 30
myCmd.Args = cobra.ExactArgs(2)  // 要求恰好 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")
// 短標誌(-n)和長標誌(--name)
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),  // 最多 1 個參數
    Aliases: []string{"ls"},         // 別名: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),  // 必須恰好 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)
        }
        
        // 確認刪除(除非使用 --force)
        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
# 重新加載 shell
exec bash  # 或 zsh

# 4. Man 頁面生成

// 在 init () 中
import "github.com/spf13/cobra/doc"
func init() {
    // 在所有命令都添加到 rootCmd 后執行
    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("輸出缺少預期內容")
    }
}

# 最佳實踐

  1. 命令組織:按功能分組子命令(如 config getconfig set
  2. 錯誤處理:使用 RunE 而不是 Run ,返回可檢查的錯誤
  3. 驗證輸入:使用 ArgsMarkFlagRequired() 驗證參數
  4. 文檔:充實 Long 字段,提供實際示例
  5. 測試:為每個命令編寫單元測試
  6. 版本管理:使用構建標誌注入版本信息
  7. 配置文件:使用 viper 與 Cobra 集成管理配置

# 與 Viper 集成(配置管理)

# 安裝 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:

  • Dockerdocker container lsdocker image build
  • kubectlkubectl get podskubectl apply -f
  • Hugohugo new posthugo server
  • Github CLIgh repo clonegh issue create

# 總結

Cobra 提供了構建專業級 CLI 工具所需的全部功能:

  • ✅ 直觀的命令結構
  • ✅ 靈活的參數解析
  • ✅ 自動幫助和完成
  • ✅ 活躍的社區和廣泛應用

無論是構建簡單的命令行工具還是複雜的 DevOps 工具,Cobra 都是 Go 開發者的首選框架。

# 參考資源

  • Cobra 官方文檔
  • Cobra GitHub 倉庫
  • Viper - Cobra 配置管理
更新於

請我喝咖啡~( ̄▽ ̄)~*