golang-cli-cobra-viper

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go CLI Development with Cobra & Viper

基于Cobra & Viper的Go CLI开发

Overview

概述

Cobra and Viper are the industry-standard libraries for building production-quality CLIs in Go. Cobra provides command structure and argument parsing, while Viper manages configuration from multiple sources with clear precedence rules.
Key Features:
  • 🎯 Cobra Commands: POSIX-compliant CLI with subcommands (
    app verb noun --flag
    )
  • ⚙️ Viper Config: Unified configuration from flags, env vars, and config files
  • 🔄 Integration: Seamless Cobra + Viper plumbing patterns
  • 🐚 Shell Completion: Auto-generated completions for bash, zsh, fish, PowerShell
  • Production Ready: Battle-tested by kubectl, docker, gh, hugo
Used By: Kubernetes (kubectl), Docker CLI, GitHub CLI (gh), Hugo, Helm, and 100+ major projects
Cobra和Viper是Go语言中构建生产级CLI的行业标准库。Cobra提供命令结构和参数解析功能,而Viper则通过清晰的优先级规则管理来自多源的配置。
核心特性:
  • 🎯 Cobra命令:符合POSIX标准的CLI,支持子命令(
    app verb noun --flag
  • ⚙️ Viper配置:统一管理来自命令行标志、环境变量和配置文件的配置
  • 🔄 集成性:Cobra + Viper的无缝集成模式
  • 🐚 Shell补全:自动生成bash、zsh、fish、PowerShell的补全脚本
  • 生产就绪:经过kubectl、docker、gh、hugo等工具的实战检验
使用案例:Kubernetes(kubectl)、Docker CLI、GitHub CLI(gh)、Hugo、Helm等100+主流项目

When to Use This Skill

何时使用该技能

Activate this skill when:
  • Building multi-command CLI tools with subcommands
  • Creating developer tools, project generators, or scaffolding utilities
  • Implementing admin CLIs for services or infrastructure
  • Requiring flexible configuration (flags > env vars > config files > defaults)
  • Adding shell completion for frequently-used CLIs
  • Building DevOps automation tools or deployment scripts
在以下场景中启用该技能:
  • 构建支持子命令的多命令CLI工具
  • 创建开发者工具、项目生成器或脚手架工具
  • 为服务或基础设施实现管理CLI
  • 需要灵活的配置(标志 > 环境变量 > 配置文件 > 默认值)
  • 为高频使用的CLI添加Shell补全功能
  • 构建DevOps自动化工具或部署脚本

Cobra Framework

Cobra框架

Command Structure Pattern

命令结构模式

Cobra follows the
APPNAME VERB NOUN --FLAG
pattern popularized by git and kubectl.
go
// cmd/root.go
package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var cfgFile string

var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "A powerful CLI tool for developers",
    Long: `MyApp is a CLI tool that demonstrates best practices
for building production-quality command-line applications.

Complete documentation is available at https://myapp.example.com`,
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func init() {
    cobra.OnInitialize(initConfig)

    // Persistent flags (available to all subcommands)
    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.myapp.yaml)")
    rootCmd.PersistentFlags().Bool("verbose", false, "verbose output")

    // Bind persistent flags to viper
    viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
    viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
}

func initConfig() {
    if cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        home, err := os.UserHomeDir()
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        viper.AddConfigPath(home)
        viper.AddConfigPath(".")
        viper.SetConfigType("yaml")
        viper.SetConfigName(".myapp")
    }

    viper.SetEnvPrefix("MYAPP")
    viper.AutomaticEnv()

    if err := viper.ReadInConfig(); err == nil {
        if viper.GetBool("verbose") {
            fmt.Println("Using config file:", viper.ConfigFileUsed())
        }
    }
}
Cobra遵循由git和kubectl普及的
APPNAME VERB NOUN --FLAG
模式。
go
// cmd/root.go
package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var cfgFile string

var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "A powerful CLI tool for developers",
    Long: `MyApp is a CLI tool that demonstrates best practices
for building production-quality command-line applications.

Complete documentation is available at https://myapp.example.com`,
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func init() {
    cobra.OnInitialize(initConfig)

    // Persistent flags (available to all subcommands)
    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.myapp.yaml)")
    rootCmd.PersistentFlags().Bool("verbose", false, "verbose output")

    // Bind persistent flags to viper
    viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
    viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
}

func initConfig() {
    if cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        home, err := os.UserHomeDir()
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        viper.AddConfigPath(home)
        viper.AddConfigPath(".")
        viper.SetConfigType("yaml")
        viper.SetConfigName(".myapp")
    }

    viper.SetEnvPrefix("MYAPP")
    viper.AutomaticEnv()

    if err := viper.ReadInConfig(); err == nil {
        if viper.GetBool("verbose") {
            fmt.Println("Using config file:", viper.ConfigFileUsed())
        }
    }
}

Subcommands with Arguments

带参数的子命令

go
// cmd/deploy.go
package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var deployCmd = &cobra.Command{
    Use:   "deploy [environment]",
    Short: "Deploy application to specified environment",
    Long: `Deploy the application to the specified environment.
Supports: dev, staging, production`,
    Args: cobra.ExactArgs(1),
    ValidArgs: []string{"dev", "staging", "production"},
    PreRunE: func(cmd *cobra.Command, args []string) error {
        // Validation logic runs before RunE
        env := args[0]
        if env == "production" && !viper.GetBool("force") {
            return fmt.Errorf("production deploys require --force flag")
        }
        return nil
    },
    RunE: func(cmd *cobra.Command, args []string) error {
        env := args[0]
        region := viper.GetString("region")
        force := viper.GetBool("force")

        fmt.Printf("Deploying to %s in region %s (force=%v)\n", env, region, force)

        // Actual deployment logic
        return deploy(env, region, force)
    },
    PostRunE: func(cmd *cobra.Command, args []string) error {
        // Cleanup or notifications
        fmt.Println("Deployment complete")
        return nil
    },
}

func init() {
    rootCmd.AddCommand(deployCmd)

    // Local flags (only for this command)
    deployCmd.Flags().StringP("region", "r", "us-east-1", "AWS region")
    deployCmd.Flags().BoolP("force", "f", false, "Force deployment without confirmation")

    // Bind flags to viper
    viper.BindPFlag("region", deployCmd.Flags().Lookup("region"))
    viper.BindPFlag("force", deployCmd.Flags().Lookup("force"))
}

func deploy(env, region string, force bool) error {
    // Implementation
    return nil
}
go
// cmd/deploy.go
package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var deployCmd = &cobra.Command{
    Use:   "deploy [environment]",
    Short: "Deploy application to specified environment",
    Long: `Deploy the application to the specified environment.
Supports: dev, staging, production`,
    Args: cobra.ExactArgs(1),
    ValidArgs: []string{"dev", "staging", "production"},
    PreRunE: func(cmd *cobra.Command, args []string) error {
        // Validation logic runs before RunE
        env := args[0]
        if env == "production" && !viper.GetBool("force") {
            return fmt.Errorf("production deploys require --force flag")
        }
        return nil
    },
    RunE: func(cmd *cobra.Command, args []string) error {
        env := args[0]
        region := viper.GetString("region")
        force := viper.GetBool("force")

        fmt.Printf("Deploying to %s in region %s (force=%v)\n", env, region, force)

        // Actual deployment logic
        return deploy(env, region, force)
    },
    PostRunE: func(cmd *cobra.Command, args []string) error {
        // Cleanup or notifications
        fmt.Println("Deployment complete")
        return nil
    },
}

func init() {
    rootCmd.AddCommand(deployCmd)

    // Local flags (only for this command)
    deployCmd.Flags().StringP("region", "r", "us-east-1", "AWS region")
    deployCmd.Flags().BoolP("force", "f", false, "Force deployment without confirmation")

    // Bind flags to viper
    viper.BindPFlag("region", deployCmd.Flags().Lookup("region"))
    viper.BindPFlag("force", deployCmd.Flags().Lookup("force"))
}

func deploy(env, region string, force bool) error {
    // Implementation
    return nil
}

Persistent vs. Local Flags

全局标志与局部标志

go
// Persistent flags: Available to command and all subcommands
rootCmd.PersistentFlags().String("config", "", "config file path")
rootCmd.PersistentFlags().Bool("verbose", false, "verbose output")

// Local flags: Only available to this specific command
deployCmd.Flags().String("region", "us-east-1", "deployment region")
deployCmd.Flags().Bool("force", false, "force deployment")

// Required flags
deployCmd.MarkFlagRequired("region")

// Flag dependencies
deployCmd.MarkFlagsRequiredTogether("username", "password")
deployCmd.MarkFlagsMutuallyExclusive("json", "yaml")
go
// Persistent flags: Available to command and all subcommands
rootCmd.PersistentFlags().String("config", "", "config file path")
rootCmd.PersistentFlags().Bool("verbose", false, "verbose output")

// Local flags: Only available to this specific command
deployCmd.Flags().String("region", "us-east-1", "deployment region")
deployCmd.Flags().Bool("force", false, "force deployment")

// Required flags
deployCmd.MarkFlagRequired("region")

// Flag dependencies
deployCmd.MarkFlagsRequiredTogether("username", "password")
deployCmd.MarkFlagsMutuallyExclusive("json", "yaml")

PreRun/PostRun Hooks

PreRun/PostRun钩子

Cobra provides execution hooks for setup and cleanup:
go
var serverCmd = &cobra.Command{
    Use:   "server",
    Short: "Start API server",

    // Execution order (all optional):
    PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
        // Runs before PreRunE, inherited by subcommands
        return setupLogging()
    },
    PreRunE: func(cmd *cobra.Command, args []string) error {
        // Validation and setup before RunE
        return validateConfig()
    },
    RunE: func(cmd *cobra.Command, args []string) error {
        // Main command logic
        return startServer()
    },
    PostRunE: func(cmd *cobra.Command, args []string) error {
        // Cleanup after RunE
        return cleanup()
    },
    PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
        // Runs after PostRunE, inherited by subcommands
        return flushLogs()
    },
}
Important: Use
RunE
,
PreRunE
,
PostRunE
(error-returning versions) instead of
Run
,
PreRun
,
PostRun
.
Cobra提供用于初始化和清理的执行钩子:
go
var serverCmd = &cobra.Command{
    Use:   "server",
    Short: "Start API server",

    // Execution order (all optional):
    PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
        // Runs before PreRunE, inherited by subcommands
        return setupLogging()
    },
    PreRunE: func(cmd *cobra.Command, args []string) error {
        // Validation and setup before RunE
        return validateConfig()
    },
    RunE: func(cmd *cobra.Command, args []string) error {
        // Main command logic
        return startServer()
    },
    PostRunE: func(cmd *cobra.Command, args []string) error {
        // Cleanup after RunE
        return cleanup()
    },
    PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
        // Runs after PostRunE, inherited by subcommands
        return flushLogs()
    },
}
重要提示:使用
RunE
PreRunE
PostRunE
(返回错误的版本)而非
Run
PreRun
PostRun

Viper Configuration Management

Viper配置管理

Configuration Priority

配置优先级

Viper follows a strict precedence order (highest to lowest):
  1. Explicit Set (
    viper.Set("key", value)
    )
  2. Command-line Flags (bound with
    viper.BindPFlag
    )
  3. Environment Variables (
    MYAPP_KEY=value
    )
  4. Config File (
    ~/.myapp.yaml
    ,
    ./config.yaml
    )
  5. Key/Value Store (etcd, Consul - optional)
  6. Defaults (
    viper.SetDefault("key", value)
    )
go
func initConfig() {
    // 1. Set defaults (lowest priority)
    viper.SetDefault("port", 8080)
    viper.SetDefault("database.host", "localhost")
    viper.SetDefault("database.port", 5432)

    // 2. Config file locations (checked in order)
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath("/etc/myapp/")
    viper.AddConfigPath("$HOME/.myapp")
    viper.AddConfigPath(".")

    // 3. Environment variables (prefix + automatic mapping)
    viper.SetEnvPrefix("MYAPP")
    viper.AutomaticEnv() // MYAPP_PORT, MYAPP_DATABASE_HOST, etc.

    // 4. Read config file (optional)
    if err := viper.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); ok {
            // Config file not found - use defaults and env vars
        } else {
            // Config file found but error reading it
            return err
        }
    }

    // 5. Flags will be bound in init() functions (highest priority)
}
Viper遵循严格的优先级顺序(从高到低):
  1. 显式设置
    viper.Set("key", value)
  2. 命令行标志(通过
    viper.BindPFlag
    绑定)
  3. 环境变量
    MYAPP_KEY=value
  4. 配置文件
    ~/.myapp.yaml
    ./config.yaml
  5. 键值存储(etcd、Consul - 可选)
  6. 默认值
    viper.SetDefault("key", value)
go
func initConfig() {
    // 1. Set defaults (lowest priority)
    viper.SetDefault("port", 8080)
    viper.SetDefault("database.host", "localhost")
    viper.SetDefault("database.port", 5432)

    // 2. Config file locations (checked in order)
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath("/etc/myapp/")
    viper.AddConfigPath("$HOME/.myapp")
    viper.AddConfigPath(".")

    // 3. Environment variables (prefix + automatic mapping)
    viper.SetEnvPrefix("MYAPP")
    viper.AutomaticEnv() // MYAPP_PORT, MYAPP_DATABASE_HOST, etc.

    // 4. Read config file (optional)
    if err := viper.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); ok {
            // Config file not found - use defaults and env vars
        } else {
            // Config file found but error reading it
            return err
        }
    }

    // 5. Flags will be bound in init() functions (highest priority)
}

Environment Variable Mapping

环境变量映射

Viper automatically maps environment variables with prefix and dot notation:
go
viper.SetEnvPrefix("MYAPP") // Prefix for env vars
viper.AutomaticEnv()        // Enable automatic mapping

// Config key → Environment variable
// "port"              → MYAPP_PORT
// "database.host"     → MYAPP_DATABASE_HOST
// "database.port"     → MYAPP_DATABASE_PORT
// "aws.s3.region"     → MYAPP_AWS_S3_REGION
Manual mapping for non-standard env var names:
go
viper.BindEnv("database.host", "DB_HOST")          // Custom env var name
viper.BindEnv("database.password", "DB_PASSWORD")  // Different naming convention
Viper会自动将带前缀的环境变量与点标记的配置键进行映射:
go
viper.SetEnvPrefix("MYAPP") // Prefix for env vars
viper.AutomaticEnv()        // Enable automatic mapping

// Config key → Environment variable
// "port"              → MYAPP_PORT
// "database.host"     → MYAPP_DATABASE_HOST
// "database.port"     → MYAPP_DATABASE_PORT
// "aws.s3.region"     → MYAPP_AWS_S3_REGION
手动映射非标准环境变量名:
go
viper.BindEnv("database.host", "DB_HOST")          // Custom env var name
viper.BindEnv("database.password", "DB_PASSWORD")  // Different naming convention

Config File Formats

配置文件格式

Viper supports multiple formats: YAML, JSON, TOML, HCL, INI, envfile, Java properties.
config.yaml:
yaml
port: 8080
log_level: info

database:
  host: localhost
  port: 5432
  user: postgres
  ssl_mode: require

aws:
  region: us-east-1
  s3:
    bucket: my-app-bucket
Accessing config values:
go
port := viper.GetInt("port")                    // 8080
dbHost := viper.GetString("database.host")      // "localhost"
s3Bucket := viper.GetString("aws.s3.bucket")    // "my-app-bucket"

// Type-safe access
if viper.IsSet("database.ssl_mode") {
    sslMode := viper.GetString("database.ssl_mode")
}

// Unmarshal into struct
type Config struct {
    Port     int    `mapstructure:"port"`
    LogLevel string `mapstructure:"log_level"`
    Database struct {
        Host    string `mapstructure:"host"`
        Port    int    `mapstructure:"port"`
        User    string `mapstructure:"user"`
        SSLMode string `mapstructure:"ssl_mode"`
    } `mapstructure:"database"`
}

var config Config
if err := viper.Unmarshal(&config); err != nil {
    return err
}
Viper支持多种格式:YAML、JSON、TOML、HCL、INI、envfile、Java属性文件。
config.yaml:
yaml
port: 8080
log_level: info

database:
  host: localhost
  port: 5432
  user: postgres
  ssl_mode: require

aws:
  region: us-east-1
  s3:
    bucket: my-app-bucket
访问配置值:
go
port := viper.GetInt("port")                    // 8080
dbHost := viper.GetString("database.host")      // "localhost"
s3Bucket := viper.GetString("aws.s3.bucket")    // "my-app-bucket"

// Type-safe access
if viper.IsSet("database.ssl_mode") {
    sslMode := viper.GetString("database.ssl_mode")
}

// Unmarshal into struct
type Config struct {
    Port     int    `mapstructure:"port"`
    LogLevel string `mapstructure:"log_level"`
    Database struct {
        Host    string `mapstructure:"host"`
        Port    int    `mapstructure:"port"`
        User    string `mapstructure:"user"`
        SSLMode string `mapstructure:"ssl_mode"`
    } `mapstructure:"database"`
}

var config Config
if err := viper.Unmarshal(&config); err != nil {
    return err
}

Cobra + Viper Integration

Cobra + Viper集成

Critical Integration Pattern

关键集成模式

The key to Cobra + Viper integration is binding flags to Viper keys:
go
// cmd/root.go
func init() {
    cobra.OnInitialize(initConfig) // Load config before command execution

    // Define flags
    rootCmd.PersistentFlags().String("config", "", "config file")
    rootCmd.PersistentFlags().String("log-level", "info", "log level")
    rootCmd.PersistentFlags().Int("port", 8080, "server port")

    // Bind flags to Viper (critical step!)
    viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
    viper.BindPFlag("log_level", rootCmd.PersistentFlags().Lookup("log-level"))
    viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
}

func initConfig() {
    // This runs BEFORE command execution via cobra.OnInitialize
    if cfgFile := viper.GetString("config"); cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        viper.AddConfigPath("$HOME/.myapp")
        viper.AddConfigPath(".")
        viper.SetConfigName("config")
    }

    viper.SetEnvPrefix("MYAPP")
    viper.AutomaticEnv()

    viper.ReadInConfig() // Ignore errors - config file is optional
}
Flag binding strategies:
go
// Strategy 1: Bind each flag individually (explicit)
viper.BindPFlag("log_level", rootCmd.Flags().Lookup("log-level"))

// Strategy 2: Bind all flags automatically (convenient)
viper.BindPFlags(rootCmd.Flags())

// Strategy 3: Hybrid approach (recommended)
// - Bind persistent flags globally
// - Bind local flags in each command's init()
rootCmd.PersistentFlags().String("config", "", "config file")
viper.BindPFlags(rootCmd.PersistentFlags())

deployCmd.Flags().String("region", "us-east-1", "AWS region")
viper.BindPFlag("deploy.region", deployCmd.Flags().Lookup("region"))
Cobra + Viper集成的核心是将标志绑定到Viper键:
go
// cmd/root.go
func init() {
    cobra.OnInitialize(initConfig) // Load config before command execution

    // Define flags
    rootCmd.PersistentFlags().String("config", "", "config file")
    rootCmd.PersistentFlags().String("log-level", "info", "log level")
    rootCmd.PersistentFlags().Int("port", 8080, "server port")

    // Bind flags to Viper (critical step!)
    viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
    viper.BindPFlag("log_level", rootCmd.PersistentFlags().Lookup("log-level"))
    viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
}

func initConfig() {
    // This runs BEFORE command execution via cobra.OnInitialize
    if cfgFile := viper.GetString("config"); cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        viper.AddConfigPath("$HOME/.myapp")
        viper.AddConfigPath(".")
        viper.SetConfigName("config")
    }

    viper.SetEnvPrefix("MYAPP")
    viper.AutomaticEnv()

    viper.ReadInConfig() // Ignore errors - config file is optional
}
标志绑定策略:
go
// Strategy 1: Bind each flag individually (explicit)
viper.BindPFlag("log_level", rootCmd.Flags().Lookup("log-level"))

// Strategy 2: Bind all flags automatically (convenient)
viper.BindPFlags(rootCmd.Flags())

// Strategy 3: Hybrid approach (recommended)
// - Bind persistent flags globally
// - Bind local flags in each command's init()
rootCmd.PersistentFlags().String("config", "", "config file")
viper.BindPFlags(rootCmd.PersistentFlags())

deployCmd.Flags().String("region", "us-east-1", "AWS region")
viper.BindPFlag("deploy.region", deployCmd.Flags().Lookup("region"))

PersistentPreRun for Config Loading

使用PersistentPreRun加载配置

Use
PersistentPreRunE
to load and validate configuration:
go
var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "My application",
    PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
        // Runs before ALL commands (inherited by subcommands)

        // 1. Validate required config
        if !viper.IsSet("api_key") {
            return fmt.Errorf("API key not configured (set MYAPP_API_KEY or add to config file)")
        }

        // 2. Setup logging based on config
        logLevel := viper.GetString("log_level")
        if err := setupLogging(logLevel); err != nil {
            return fmt.Errorf("invalid log level: %w", err)
        }

        // 3. Initialize clients/services
        apiKey := viper.GetString("api_key")
        if err := initAPIClient(apiKey); err != nil {
            return fmt.Errorf("failed to initialize API client: %w", err)
        }

        return nil
    },
}
使用
PersistentPreRunE
加载并验证配置:
go
var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "My application",
    PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
        // Runs before ALL commands (inherited by subcommands)

        // 1. Validate required config
        if !viper.IsSet("api_key") {
            return fmt.Errorf("API key not configured (set MYAPP_API_KEY or add to config file)")
        }

        // 2. Setup logging based on config
        logLevel := viper.GetString("log_level")
        if err := setupLogging(logLevel); err != nil {
            return fmt.Errorf("invalid log level: %w", err)
        }

        // 3. Initialize clients/services
        apiKey := viper.GetString("api_key")
        if err := initAPIClient(apiKey); err != nil {
            return fmt.Errorf("failed to initialize API client: %w", err)
        }

        return nil
    },
}

Shell Completion

Shell补全

Cobra generates shell completion scripts for bash, zsh, fish, and PowerShell.
Cobra可为bash、zsh、fish和PowerShell生成Shell补全脚本。

Adding Completion Command

添加补全命令

go
// cmd/completion.go
package cmd

import (
    "os"

    "github.com/spf13/cobra"
)

var completionCmd = &cobra.Command{
    Use:   "completion [bash|zsh|fish|powershell]",
    Short: "Generate shell completion script",
    Long: `Generate shell completion script for myapp.

To load completions:

Bash:
  $ source <(myapp completion bash)
  # To load automatically, add to ~/.bashrc:
  $ echo 'source <(myapp completion bash)' >> ~/.bashrc

Zsh:
  $ source <(myapp completion zsh)
  # To load automatically, add to ~/.zshrc:
  $ echo 'source <(myapp completion zsh)' >> ~/.zshrc

Fish:
  $ myapp completion fish | source
  # To load automatically:
  $ myapp completion fish > ~/.config/fish/completions/myapp.fish

PowerShell:
  PS> myapp completion powershell | Out-String | Invoke-Expression
  # To load automatically, add to PowerShell profile.
`,
    DisableFlagsInUseLine: true,
    ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
    Args:                  cobra.ExactValidArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        switch args[0] {
        case "bash":
            return cmd.Root().GenBashCompletion(os.Stdout)
        case "zsh":
            return cmd.Root().GenZshCompletion(os.Stdout)
        case "fish":
            return cmd.Root().GenFishCompletion(os.Stdout, true)
        case "powershell":
            return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
        }
        return nil
    },
}

func init() {
    rootCmd.AddCommand(completionCmd)
}
go
// cmd/completion.go
package cmd

import (
    "os"

    "github.com/spf13/cobra"
)

var completionCmd = &cobra.Command{
    Use:   "completion [bash|zsh|fish|powershell]",
    Short: "Generate shell completion script",
    Long: `Generate shell completion script for myapp.

To load completions:

Bash:
  $ source <(myapp completion bash)
  # To load automatically, add to ~/.bashrc:
  $ echo 'source <(myapp completion bash)' >> ~/.bashrc

Zsh:
  $ source <(myapp completion zsh)
  # To load automatically, add to ~/.zshrc:
  $ echo 'source <(myapp completion zsh)' >> ~/.zshrc

Fish:
  $ myapp completion fish | source
  # To load automatically:
  $ myapp completion fish > ~/.config/fish/completions/myapp.fish

PowerShell:
  PS> myapp completion powershell | Out-String | Invoke-Expression
  # To load automatically, add to PowerShell profile.
`,
    DisableFlagsInUseLine: true,
    ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
    Args:                  cobra.ExactValidArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        switch args[0] {
        case "bash":
            return cmd.Root().GenBashCompletion(os.Stdout)
        case "zsh":
            return cmd.Root().GenZshCompletion(os.Stdout)
        case "fish":
            return cmd.Root().GenFishCompletion(os.Stdout, true)
        case "powershell":
            return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
        }
        return nil
    },
}

func init() {
    rootCmd.AddCommand(completionCmd)
}

Custom Completion Functions

自定义补全函数

Provide dynamic completions for command arguments:
go
var deployCmd = &cobra.Command{
    Use:   "deploy [environment]",
    Short: "Deploy to environment",
    Args:  cobra.ExactArgs(1),
    ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
        // Return available environments
        envs := []string{"dev", "staging", "production"}
        return envs, cobra.ShellCompDirectiveNoFileComp
    },
    RunE: func(cmd *cobra.Command, args []string) error {
        return deploy(args[0])
    },
}

// Custom flag completion
deployCmd.RegisterFlagCompletionFunc("region", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    regions := []string{"us-east-1", "us-west-2", "eu-west-1"}
    return regions, cobra.ShellCompDirectiveNoFileComp
})
为命令参数提供动态补全:
go
var deployCmd = &cobra.Command{
    Use:   "deploy [environment]",
    Short: "Deploy to environment",
    Args:  cobra.ExactArgs(1),
    ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
        // Return available environments
        envs := []string{"dev", "staging", "production"}
        return envs, cobra.ShellCompDirectiveNoFileComp
    },
    RunE: func(cmd *cobra.Command, args []string) error {
        return deploy(args[0])
    },
}

// Custom flag completion
deployCmd.RegisterFlagCompletionFunc("region", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    regions := []string{"us-east-1", "us-west-2", "eu-west-1"}
    return regions, cobra.ShellCompDirectiveNoFileComp
})

CLI Best Practices

CLI最佳实践

User-Friendly Error Messages

用户友好的错误消息

go
// ❌ BAD: Technical jargon
return fmt.Errorf("db connection failed: EOF")

// ✅ GOOD: Actionable error message
return fmt.Errorf("cannot connect to database at %s:%d\nPlease check:\n  - Database is running\n  - Credentials are correct (MYAPP_DB_PASSWORD)\n  - Network connectivity", host, port)

// ✅ GOOD: Suggest remediation
if !viper.IsSet("api_key") {
    return fmt.Errorf("API key not configured\nSet environment variable: export MYAPP_API_KEY=your-key\nOr add to config file: ~/.myapp.yaml")
}
go
// ❌ 错误示例:技术术语过多
return fmt.Errorf("db connection failed: EOF")

// ✅ 正确示例:可执行的错误消息
return fmt.Errorf("无法连接到数据库 %s:%d\n请检查:\n  - 数据库是否运行\n  - 凭证是否正确(MYAPP_DB_PASSWORD)\n  - 网络连通性", host, port)

// ✅ 正确示例:提供修复建议
if !viper.IsSet("api_key") {
    return fmt.Errorf("未配置API密钥\n设置环境变量:export MYAPP_API_KEY=your-key\n或添加到配置文件:~/.myapp.yaml")
}

Progress Indicators

进度指示器

go
import "github.com/briandowns/spinner"

func deploy(env string) error {
    s := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
    s.Suffix = " Deploying to " + env + "..."
    s.Start()
    defer s.Stop()

    // Deployment logic
    if err := performDeployment(env); err != nil {
        s.Stop()
        return err
    }

    s.Stop()
    fmt.Println("✓ Deployment successful")
    return nil
}
go
import "github.com/briandowns/spinner"

func deploy(env string) error {
    s := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
    s.Suffix = " Deploying to " + env + "..."
    s.Start()
    defer s.Stop()

    // Deployment logic
    if err := performDeployment(env); err != nil {
        s.Stop()
        return err
    }

    s.Stop()
    fmt.Println("✓ 部署成功")
    return nil
}

Output Formatting

输出格式化

go
import (
    "encoding/json"
    "github.com/olekukonko/tablewriter"
)

func displayResults(items []Item, format string) error {
    switch format {
    case "json":
        enc := json.NewEncoder(os.Stdout)
        enc.SetIndent("", "  ")
        return enc.Encode(items)

    case "table":
        table := tablewriter.NewWriter(os.Stdout)
        table.SetHeader([]string{"ID", "Name", "Status"})
        for _, item := range items {
            table.Append([]string{item.ID, item.Name, item.Status})
        }
        table.Render()
        return nil

    default:
        return fmt.Errorf("unknown format: %s (use json or table)", format)
    }
}
go
import (
    "encoding/json"
    "github.com/olekukonko/tablewriter"
)

func displayResults(items []Item, format string) error {
    switch format {
    case "json":
        enc := json.NewEncoder(os.Stdout)
        enc.SetIndent("", "  ")
        return enc.Encode(items)

    case "table":
        table := tablewriter.NewWriter(os.Stdout)
        table.SetHeader([]string{"ID", "名称", "状态"})
        for _, item := range items {
            table.Append([]string{item.ID, item.Name, item.Status})
        }
        table.Render()
        return nil

    default:
        return fmt.Errorf("未知格式:%s(请使用json或table)", format)
    }
}

Logging vs. User Output

日志与用户输出分离

go
import (
    "log"
    "os"
)

var (
    // User-facing output (stdout)
    out = os.Stdout

    // Logging and errors (stderr)
    logger = log.New(os.Stderr, "[myapp] ", log.LstdFlags)
)

func RunCommand() error {
    // User output: stdout
    fmt.Fprintln(out, "Processing files...")

    // Debug/verbose logging: stderr
    if viper.GetBool("verbose") {
        logger.Println("Reading config from", viper.ConfigFileUsed())
    }

    // Errors: stderr
    if err := process(); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        return err
    }

    // Success message: stdout
    fmt.Fprintln(out, "✓ Complete")
    return nil
}
go
import (
    "log"
    "os"
)

var (
    // 用户面向的输出(stdout)
    out = os.Stdout

    // 日志和错误输出(stderr)
    logger = log.New(os.Stderr, "[myapp] ", log.LstdFlags)
)

func RunCommand() error {
    // 用户输出:stdout
    fmt.Fprintln(out, "正在处理文件...")

    // 调试/详细日志:stderr
    if viper.GetBool("verbose") {
        logger.Println("正在从", viper.ConfigFileUsed(), "读取配置")
    }

    // 错误输出:stderr
    if err := process(); err != nil {
        fmt.Fprintf(os.Stderr, "错误:%v\n", err)
        return err
    }

    // 成功消息:stdout
    fmt.Fprintln(out, "✓ 完成")
    return nil
}

Testing CLI Applications

测试CLI应用

Testing Command Execution

测试命令执行

go
// cmd/deploy_test.go
package cmd

import (
    "bytes"
    "testing"

    "github.com/spf13/cobra"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func executeCommand(root *cobra.Command, args ...string) (output string, err error) {
    buf := new(bytes.Buffer)
    root.SetOut(buf)
    root.SetErr(buf)
    root.SetArgs(args)

    err = root.Execute()
    return buf.String(), err
}

func TestDeployCommand(t *testing.T) {
    tests := []struct {
        name    string
        args    []string
        wantErr bool
        wantOut string
    }{
        {
            name:    "deploy to dev",
            args:    []string{"deploy", "dev"},
            wantErr: false,
            wantOut: "Deploying to dev",
        },
        {
            name:    "deploy to production without force",
            args:    []string{"deploy", "production"},
            wantErr: true,
            wantOut: "production deploys require --force flag",
        },
        {
            name:    "deploy to production with force",
            args:    []string{"deploy", "production", "--force"},
            wantErr: false,
            wantOut: "Deploying to production",
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            output, err := executeCommand(rootCmd, tt.args...)

            if tt.wantErr {
                require.Error(t, err)
            } else {
                require.NoError(t, err)
            }

            assert.Contains(t, output, tt.wantOut)
        })
    }
}
go
// cmd/deploy_test.go
package cmd

import (
    "bytes"
    "testing"

    "github.com/spf13/cobra"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func executeCommand(root *cobra.Command, args ...string) (output string, err error) {
    buf := new(bytes.Buffer)
    root.SetOut(buf)
    root.SetErr(buf)
    root.SetArgs(args)

    err = root.Execute()
    return buf.String(), err
}

func TestDeployCommand(t *testing.T) {
    tests := []struct {
        name    string
        args    []string
        wantErr bool
        wantOut string
    }{
        {
            name:    "部署到dev环境",
            args:    []string{"deploy", "dev"},
            wantErr: false,
            wantOut: "Deploying to dev",
        },
        {
            name:    "未加force标志部署到生产环境",
            args:    []string{"deploy", "production"},
            wantErr: true,
            wantOut: "production deploys require --force flag",
        },
        {
            name:    "添加force标志部署到生产环境",
            args:    []string{"deploy", "production", "--force"},
            wantErr: false,
            wantOut: "Deploying to production",
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            output, err := executeCommand(rootCmd, tt.args...)

            if tt.wantErr {
                require.Error(t, err)
            } else {
                require.NoError(t, err)
            }

            assert.Contains(t, output, tt.wantOut)
        })
    }
}

Testing with Viper Configuration

结合Viper配置测试

go
func TestCommandWithConfig(t *testing.T) {
    // Reset viper state before each test
    viper.Reset()

    // Set test configuration
    viper.Set("region", "eu-west-1")
    viper.Set("api_key", "test-key-123")

    output, err := executeCommand(rootCmd, "deploy", "staging")

    require.NoError(t, err)
    assert.Contains(t, output, "eu-west-1")
}
go
func TestCommandWithConfig(t *testing.T) {
    // 每次测试前重置Viper状态
    viper.Reset()

    // 设置测试配置
    viper.Set("region", "eu-west-1")
    viper.Set("api_key", "test-key-123")

    output, err := executeCommand(rootCmd, "deploy", "staging")

    require.NoError(t, err)
    assert.Contains(t, output, "eu-west-1")
}

Capturing Output

捕获输出

go
func TestOutputFormat(t *testing.T) {
    // Capture stdout
    oldStdout := os.Stdout
    r, w, _ := os.Pipe()
    os.Stdout = w
    defer func() { os.Stdout = oldStdout }()

    // Execute command
    err := listCmd.RunE(listCmd, []string{})
    require.NoError(t, err)

    // Read output
    w.Close()
    var buf bytes.Buffer
    io.Copy(&buf, r)
    output := buf.String()

    assert.Contains(t, output, "ID")
    assert.Contains(t, output, "Name")
}
go
func TestOutputFormat(t *testing.T) {
    // 捕获stdout
    oldStdout := os.Stdout
    r, w, _ := os.Pipe()
    os.Stdout = w
    defer func() { os.Stdout = oldStdout }()

    // 执行命令
    err := listCmd.RunE(listCmd, []string{})
    require.NoError(t, err)

    // 读取输出
    w.Close()
    var buf bytes.Buffer
    io.Copy(&buf, r)
    output := buf.String()

    assert.Contains(t, output, "ID")
    assert.Contains(t, output, "名称")
}

Decision Trees

决策树

When to Use Cobra

何时使用Cobra

Use Cobra when:
  • ✅ Building multi-command CLI with subcommands (e.g.,
    git clone
    ,
    docker run
    )
  • ✅ Need POSIX-compliant flag parsing (
    --flag
    ,
    -f
    )
  • ✅ Want built-in help generation (
    --help
    )
  • ✅ Require shell completion support
  • ✅ Building professional CLI used by other developers
Don't use Cobra when:
  • ❌ Simple single-command script (use
    flag
    package)
  • ❌ Internal-only tool with 1-2 flags
  • ❌ Prototyping or throwaway scripts
使用Cobra的场景:
  • ✅ 构建支持子命令的多命令CLI(如
    git clone
    docker run
  • ✅ 需要符合POSIX标准的标志解析(
    --flag
    -f
  • ✅ 想要自动生成帮助文档(
    --help
  • ✅ 需要Shell补全支持
  • ✅ 构建供其他开发者使用的专业CLI
不使用Cobra的场景:
  • ❌ 简单的单命令脚本(使用
    flag
    包即可)
  • ❌ 仅含1-2个标志的内部工具
  • ❌ 原型或一次性脚本

When to Use Viper

何时使用Viper

Use Viper when:
  • ✅ Need configuration from multiple sources (flags, env vars, files)
  • ✅ Want clear configuration precedence rules
  • ✅ Support multiple config file formats (YAML, JSON, TOML)
  • ✅ Require environment variable mapping with prefixes
  • ✅ Need live config reloading (watch config file changes)
Don't use Viper when:
  • ❌ Only using command-line flags (Cobra alone is sufficient)
  • ❌ Hardcoded configuration values
  • ❌ Simple scripts with no configuration
使用Viper的场景:
  • ✅ 需要从多源加载配置(标志、环境变量、文件)
  • ✅ 想要清晰的配置优先级规则
  • ✅ 支持多种配置文件格式(YAML、JSON、TOML)
  • ✅ 需要带前缀的环境变量映射
  • ✅ 需要配置热重载(监听配置文件变化)
不使用Viper的场景:
  • ❌ 仅使用命令行标志(单独使用Cobra即可)
  • ❌ 硬编码配置值
  • ❌ 无配置需求的简单脚本

When to Add Shell Completion

何时添加Shell补全

Add shell completion when:
  • ✅ CLI used frequently by developers (daily/hourly)
  • ✅ Many subcommands or complex flag combinations
  • ✅ Arguments have known valid values (e.g., environments, regions)
  • ✅ Building professional developer tools
Skip shell completion when:
  • ❌ CLI used rarely (monthly or less)
  • ❌ Simple commands with few options
  • ❌ Internal-only tools
添加Shell补全的场景:
  • ✅ CLI被开发者高频使用(每日/每小时)
  • ✅ 包含多个子命令或复杂的标志组合
  • ✅ 参数有已知的有效值(如环境、区域)
  • ✅ 构建专业的开发者工具
跳过Shell补全的场景:
  • ❌ CLI使用频率低(每月或更少)
  • ❌ 命令简单、选项少
  • ❌ 仅内部使用的工具

When to Use Persistent Flags

何时使用全局标志

Use persistent flags when:
  • ✅ Flag applies to ALL subcommands (e.g.,
    --verbose
    ,
    --config
    )
  • ✅ Common configuration shared across commands
  • ✅ Global behavior modifiers (e.g.,
    --dry-run
    ,
    --output
    )
Use local flags when:
  • ✅ Flag only relevant to specific command
  • ✅ Command-specific parameters (e.g.,
    --region
    for deploy command)
使用全局标志的场景:
  • ✅ 标志适用于所有子命令(如
    --verbose
    --config
  • ✅ 命令间共享的通用配置
  • ✅ 全局行为修改器(如
    --dry-run
    --output
使用局部标志的场景:
  • ✅ 标志仅与特定命令相关
  • ✅ 命令专属参数(如deploy命令的
    --region

Anti-Patterns

反模式

❌ Not Handling Errors in PreRunE/RunE

❌ 未在PreRunE/RunE中处理错误

Wrong:
go
var deployCmd = &cobra.Command{
    Use: "deploy",
    Run: func(cmd *cobra.Command, args []string) {
        deploy(args[0]) // Ignores error!
    },
}
Correct:
go
var deployCmd = &cobra.Command{
    Use: "deploy",
    RunE: func(cmd *cobra.Command, args []string) error {
        return deploy(args[0]) // Proper error handling
    },
}
错误示例:
go
var deployCmd = &cobra.Command{
    Use: "deploy",
    Run: func(cmd *cobra.Command, args []string) {
        deploy(args[0]) // 忽略错误!
    },
}
正确示例:
go
var deployCmd = &cobra.Command{
    Use: "deploy",
    RunE: func(cmd *cobra.Command, args []string) error {
        return deploy(args[0]) // 正确处理错误
    },
}

❌ Mixing Configuration Sources Without Clear Precedence

❌ 混合配置源且无清晰优先级

Wrong:
go
// Confusing: Which takes precedence?
config.Port = viper.GetInt("port")
if os.Getenv("PORT") != "" {
    config.Port = atoi(os.Getenv("PORT"))
}
if *flagPort != 0 {
    config.Port = *flagPort
}
Correct:
go
// Clear: Viper handles precedence automatically
viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
viper.SetEnvPrefix("MYAPP")
viper.AutomaticEnv()
viper.SetDefault("port", 8080)

config.Port = viper.GetInt("port") // Respects: flag > env > config > default
错误示例:
go
// 混淆:哪个优先级更高?
config.Port = viper.GetInt("port")
if os.Getenv("PORT") != "" {
    config.Port = atoi(os.Getenv("PORT"))
}
if *flagPort != 0 {
    config.Port = *flagPort
}
正确示例:
go
// 清晰:Viper自动处理优先级
viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
viper.SetEnvPrefix("MYAPP")
viper.AutomaticEnv()
viper.SetDefault("port", 8080)

config.Port = viper.GetInt("port") // 遵循:标志 > 环境变量 > 配置文件 > 默认值

❌ Forgetting to Bind Flags to Viper

❌ 忘记将标志绑定到Viper

Wrong:
go
rootCmd.Flags().String("region", "us-east-1", "AWS region")
// Flag not bound to Viper - won't respect precedence!

func deploy() {
    region := viper.GetString("region") // Always returns config file value
}
Correct:
go
rootCmd.Flags().String("region", "us-east-1", "AWS region")
viper.BindPFlag("region", rootCmd.Flags().Lookup("region")) // Bind it!

func deploy() {
    region := viper.GetString("region") // Respects flag > env > config
}
错误示例:
go
rootCmd.Flags().String("region", "us-east-1", "AWS region")
// 标志未绑定到Viper - 不遵循优先级!

func deploy() {
    region := viper.GetString("region") // 始终返回配置文件中的值
}
正确示例:
go
rootCmd.Flags().String("region", "us-east-1", "AWS region")
viper.BindPFlag("region", rootCmd.Flags().Lookup("region")) // 绑定标志!

func deploy() {
    region := viper.GetString("region") // 遵循:标志 > 环境变量 > 配置文件
}

❌ Not Testing CLI Commands

❌ 未测试CLI命令

Wrong:
go
// No tests for CLI commands - bugs slip through
Correct:
go
func TestDeployCommand(t *testing.T) {
    output, err := executeCommand(rootCmd, "deploy", "staging", "--region", "eu-west-1")
    require.NoError(t, err)
    assert.Contains(t, output, "Deploying to staging")
    assert.Contains(t, output, "eu-west-1")
}
错误示例:
go
// 无CLI命令测试 - 漏洞容易被忽略
正确示例:
go
func TestDeployCommand(t *testing.T) {
    output, err := executeCommand(rootCmd, "deploy", "staging", "--region", "eu-west-1")
    require.NoError(t, err)
    assert.Contains(t, output, "Deploying to staging")
    assert.Contains(t, output, "eu-west-1")
}

❌ Poor Error Messages

❌ 错误消息质量差

Wrong:
go
return fmt.Errorf("connection failed") // Unhelpful
Correct:
go
return fmt.Errorf("cannot connect to database at %s:%d\nCheck:\n  - Database is running\n  - Credentials (MYAPP_DB_PASSWORD)\n  - Firewall rules", host, port)
错误示例:
go
return fmt.Errorf("连接失败") // 无帮助
正确示例:
go
return fmt.Errorf("无法连接到数据库 %s:%d\n检查:\n  - 数据库是否运行\n  - 凭证(MYAPP_DB_PASSWORD)\n  - 防火墙规则", host, port)

❌ Using Run Instead of RunE

❌ 使用Run而非RunE

Wrong:
go
var rootCmd = &cobra.Command{
    Use: "myapp",
    Run: func(cmd *cobra.Command, args []string) {
        if err := execute(); err != nil {
            fmt.Println(err) // Error not propagated
        }
    },
}
Correct:
go
var rootCmd = &cobra.Command{
    Use: "myapp",
    RunE: func(cmd *cobra.Command, args []string) error {
        return execute() // Cobra handles error display and exit code
    },
}
错误示例:
go
var rootCmd = &cobra.Command{
    Use: "myapp",
    Run: func(cmd *cobra.Command, args []string) {
        if err := execute(); err != nil {
            fmt.Println(err) // 错误未被传播
        }
    },
}
正确示例:
go
var rootCmd = &cobra.Command{
    Use: "myapp",
    RunE: func(cmd *cobra.Command, args []string) error {
        return execute() // Cobra处理错误显示和退出码
    },
}

Production Example

生产示例

Minimal production-ready CLI structure:
myapp/
├── cmd/
│   ├── root.go          # Root command + config loading
│   ├── deploy.go        # Deploy subcommand
│   ├── status.go        # Status subcommand
│   └── completion.go    # Shell completion
├── main.go              # Entry point
├── config.yaml          # Example config file
└── go.mod
main.go:
go
package main

import "myapp/cmd"

func main() {
    cmd.Execute()
}
cmd/root.go: See "Command Structure Pattern" section above
Building and installing:
bash
undefined
最小化生产就绪CLI结构:
myapp/
├── cmd/
│   ├── root.go          # 根命令 + 配置加载
│   ├── deploy.go        # 部署子命令
│   ├── status.go        # 状态子命令
│   └── completion.go    # Shell补全
├── main.go              # 入口文件
├── config.yaml          # 示例配置文件
└── go.mod
main.go:
go
package main

import "myapp/cmd"

func main() {
    cmd.Execute()
}
cmd/root.go:参见上方「命令结构模式」章节
构建与安装:
bash
undefined

Development

开发模式

go run main.go deploy staging --region us-west-2
go run main.go deploy staging --region us-west-2

Production build

生产构建

go build -o myapp
go build -o myapp

Install globally

全局安装

go install
go install

Enable shell completion

启用Shell补全

myapp completion bash > /etc/bash_completion.d/myapp
undefined
myapp completion bash > /etc/bash_completion.d/myapp
undefined

Resources

资源

Official Documentation:
Learning Resources:
  • "Building CLI Apps in Go with Cobra & Viper" (November 2025) - Comprehensive tutorial
  • "The Cobra & Viper Journey" - Learning path for CLI development
  • Cobra Generator - Scaffolding tool for new CLIs
Production Examples:
Related Skills:
  • golang-testing-strategies
    - Testing CLI commands comprehensively
  • golang-http-servers
    - Building API servers with configuration
  • golang-concurrency-patterns
    - Async operations in CLI tools
官方文档:
学习资源:
  • "Building CLI Apps in Go with Cobra & Viper"(2025年11月)- 全面教程
  • "The Cobra & Viper Journey" - CLI开发学习路径
  • Cobra生成器 - 新CLI脚手架工具
生产示例:
相关技能:
  • golang-testing-strategies
    - 全面测试CLI命令
  • golang-http-servers
    - 结合配置构建API服务
  • golang-concurrency-patterns
    - CLI工具中的异步操作

Success Criteria

成功标准

You know you've mastered Cobra + Viper when:
  • ✅ Commands follow POSIX conventions (
    VERB NOUN --FLAG
    )
  • ✅ Configuration precedence is clear: flags > env > config > defaults
  • ✅ All flags bound to Viper for unified config access
  • ✅ Shell completion generated for all major shells
  • ✅ Error messages are actionable and user-friendly
  • ✅ CLI commands have comprehensive tests
  • ✅ Help text auto-generated and accurate
  • ✅ PersistentPreRunE used for global setup/validation
  • ✅ Separation of concerns: user output (stdout) vs. logging (stderr)
  • ✅ Config files optional - CLI works with flags/env vars alone
当你掌握Cobra + Viper时,你能够:
  • ✅ 命令遵循POSIX规范(
    VERB NOUN --FLAG
  • ✅ 配置优先级清晰:标志 > 环境变量 > 配置文件 > 默认值
  • ✅ 所有标志绑定到Viper以实现统一配置访问
  • ✅ 为所有主流Shell生成补全脚本
  • ✅ 错误消息可执行且用户友好
  • ✅ CLI命令有全面的测试
  • ✅ 帮助文本自动生成且准确
  • ✅ 使用PersistentPreRunE进行全局初始化/验证
  • ✅ 关注点分离:用户输出(stdout)与日志(stderr)
  • ✅ 配置文件可选 - CLI仅通过标志/环境变量即可工作