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 alice@example.com
# 启动服务器
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)
}
}
🏢 业务场景
- DevOps 工具:kubectl 风格的 CLI 工具
- 数据库迁移:migrate up/down/status 命令
- 代码生成:scaffold 命令生成项目模板
- 管理工具:admin user create/list/delete 子命令
- 部署工具:deploy staging/production 环境