强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Go 语言完全指南 / 26 - CLI 开发:cobra、pflag、交互式命令

26 - CLI 开发

26.1 标准库 flag

package main

import (
    "flag"
    "fmt"
)

func main() {
    // 定义参数
    name := flag.String("name", "World", "名字")
    age := flag.Int("age", 0, "年龄")
    verbose := flag.Bool("v", false, "详细模式")

    // 自定义参数
    var ports []int
    flag.Func("port", "端口号", func(s string) error {
        // 解析端口号
        return nil
    })

    flag.Parse()

    fmt.Printf("名字: %s, 年龄: %d, 详细: %v\n", *name, *age, *verbose)
    fmt.Println("额外参数:", flag.Args())
}
go run main.go -name Alice -age 30 -v extra args

26.2 Cobra

Cobra 是 Go 最流行的 CLI 框架,被 Kubernetes、Docker、Hugo 等项目使用。

安装

go install github.com/spf13/cobra-cli@latest
cobra-cli init mycli

基本结构

package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
    Use:   "mycli",
    Short: "我的 CLI 工具",
    Long:  `这是一个使用 Cobra 构建的 CLI 工具示例`,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("欢迎使用 mycli!")
    },
}

var versionCmd = &cobra.Command{
    Use:   "version",
    Short: "显示版本号",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("v1.0.0")
    },
}

var serveCmd = &cobra.Command{
    Use:   "serve",
    Short: "启动服务器",
    Run: func(cmd *cobra.Command, args []string) {
        port, _ := cmd.Flags().GetInt("port")
        fmt.Printf("服务器启动在 :%d\n", port)
    },
}

func init() {
    rootCmd.AddCommand(versionCmd)
    rootCmd.AddCommand(serveCmd)

    // 添加 flag
    serveCmd.Flags().IntP("port", "p", 8080, "服务器端口")
    serveCmd.Flags().StringP("host", "H", "localhost", "服务器地址")
    serveCmd.Flags().BoolP("debug", "d", false, "调试模式")

    // 绑定到 viper
    // viper.BindPFlag("port", serveCmd.Flags().Lookup("port"))
}

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

带参数的命令

var greetCmd = &cobra.Command{
    Use:   "greet [name]",
    Short: "打招呼",
    Args:  cobra.MinimumNArgs(1), // 至少 1 个参数
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Printf("你好, %s!\n", args[0])
    },
}

var createCmd = &cobra.Command{
    Use:   "create",
    Short: "创建资源",
}

var createUserCmd = &cobra.Command{
    Use:   "user [name]",
    Short: "创建用户",
    Args:  cobra.ExactArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        email, _ := cmd.Flags().GetString("email")
        if email == "" {
            return fmt.Errorf("--email is required")
        }
        fmt.Printf("创建用户: %s (%s)\n", args[0], email)
        return nil
    },
}

func init() {
    rootCmd.AddCommand(greetCmd)
    createCmd.AddCommand(createUserCmd)
    rootCmd.AddCommand(createCmd)
    createUserCmd.Flags().String("email", "", "邮箱地址")
}

持久化标志(全局标志)

var cfgFile string
var verbose bool

func init() {
    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "配置文件")
    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "详细输出")
}

消息模板

var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "简短描述",
    Long:  `长描述,支持多行文本`,
    Example: `  # 创建用户
  myapp create user Alice --email [email protected]

  # 启动服务器
  myapp serve --port 3000`,
    Run: func(cmd *cobra.Command, args []string) {},
}

26.3 pflag

Cobra 底层使用 pflag(POSIX flags),支持更标准的命令行参数。

import flag "github.com/spf13/pflag"

func main() {
    name := flag.String("name", "World", "名字")
    port := flag.IntP("port", "p", 8080, "端口")
    debug := flag.Bool("debug", false, "调试模式")
    flag.Parse()

    fmt.Println(*name, *port, *debug)
}

26.4 交互式命令

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

// 简单交互
func prompt(message string) string {
    fmt.Print(message)
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Scan()
    return strings.TrimSpace(scanner.Text())
}

// 带默认值的确认
func confirm(message string, defaultYes bool) bool {
    hint := "[y/N]"
    if defaultYes {
        hint = "[Y/n]"
    }
    answer := prompt(fmt.Sprintf("%s %s: ", message, hint))
    if answer == "" {
        return defaultYes
    }
    return strings.ToLower(answer) == "y"
}

// 选择列表
func choose(message string, options []string) (int, string) {
    fmt.Println(message)
    for i, opt := range options {
        fmt.Printf("  [%d] %s\n", i+1, opt)
    }
    answer := prompt("请选择: ")
    idx := 0
    fmt.Sscanf(answer, "%d", &idx)
    if idx < 1 || idx > len(options) {
        return -1, ""
    }
    return idx - 1, options[idx-1]
}

// 密码输入(隐藏)
func readPassword(prompt string) string {
    fmt.Print(prompt)
    // 需要使用 golang.org/x/term
    // bytePassword, _ := term.ReadPassword(int(syscall.Stdin))
    // return string(bytePassword)
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Scan()
    return scanner.Text()
}

func main() {
    name := prompt("请输入你的名字: ")
    fmt.Printf("你好, %s!\n", name)

    if confirm("是否继续?", true) {
        fmt.Println("继续执行...")
    }

    _, choice := choose("选择颜色:", []string{"红色", "绿色", "蓝色"})
    fmt.Println("你选择了:", choice)
}

使用 survey 库

import "github.com/AlecAivazis/survey/v2"

func main() {
    // 文本输入
    name := ""
    prompt := &survey.Input{
        Message: "请输入你的名字:",
    }
    survey.AskOne(prompt, &name)

    // 选择
    color := ""
    selectPrompt := &survey.Select{
        Message: "选择颜色:",
        Options: []string{"红色", "绿色", "蓝色"},
    }
    survey.AskOne(selectPrompt, &color)

    // 多选
    langs := []string{}
    multiPrompt := &survey.MultiSelect{
        Message: "选择语言:",
        Options: []string{"Go", "Python", "Rust", "JavaScript"},
    }
    survey.AskOne(multiPrompt, &langs)

    // 确认
    confirm := false
    confirmPrompt := &survey.Confirm{
        Message: "确认提交?",
    }
    survey.AskOne(confirmPrompt, &confirm)
}

26.5 进度条

import "github.com/schollz/progressbar/v3"

func main() {
    bar := progressbar.Default(100)
    for i := 0; i < 100; i++ {
        bar.Add(1)
        time.Sleep(50 * time.Millisecond)
    }
    fmt.Println("完成!")
}

26.6 完整 CLI 示例

package main

import (
    "fmt"
    "os"
    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

type Config struct {
    Server ServerConfig `mapstructure:"server"`
    DB     DBConfig     `mapstructure:"database"`
}

var rootCmd = &cobra.Command{
    Use:   "mytool",
    Short: "我的工具",
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
        initConfig()
    },
}

var serveCmd = &cobra.Command{
    Use:   "serve",
    Short: "启动服务器",
    RunE: func(cmd *cobra.Command, args []string) error {
        cfg := loadConfig()
        return startServer(cfg)
    },
}

func init() {
    rootCmd.PersistentFlags().String("config", "", "配置文件")
    rootCmd.PersistentFlags().Bool("verbose", false, "详细输出")
    
    serveCmd.Flags().IntP("port", "p", 8080, "端口")
    viper.BindPFlag("server.port", serveCmd.Flags().Lookup("port"))

    rootCmd.AddCommand(serveCmd)
}

func initConfig() {
    cfgFile, _ := rootCmd.Flags().GetString("config")
    if cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        viper.AddConfigPath(".")
        viper.SetConfigName("config")
        viper.SetConfigType("yaml")
    }
    viper.AutomaticEnv()
    viper.ReadInConfig()
}

func main() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

🏢 业务场景

  1. DevOps 工具:kubectl 风格的 CLI 工具
  2. 数据库迁移:migrate up/down/status 命令
  3. 代码生成:scaffold 命令生成项目模板
  4. 管理工具:admin user create/list/delete 子命令
  5. 部署工具:deploy staging/production 环境

📖 扩展阅读