Pflag 详细使用教程
1. Pflag 基础概念与安装
1.1 Pflag 简介
Pflag 是 Go 语言标准库 flag 的增强版,提供 POSIX/GNU 风格的命令行参数解析。它被广泛应用于 Docker、Kubernetes 等大型 Go 项目。
1.2 安装
go get github.com/spf13/pflag
1.3 基础导入
import (
"fmt"
flag "github.com/spf13/pflag" // 使用别名避免与标准库冲突
)
2. Pflag 核心功能详解
2.1 基础数据类型支持
2.1.1 基本类型定义
package main
import (
"fmt"
"net"
"time"
flag "github.com/spf13/pflag"
)
func main() {
// 字符串类型
var name = flag.String("name", "default", "字符串参数")
// 整数类型
var count = flag.Int("count", 1, "整数参数")
var count64 = flag.Int64("count64", 1, "64位整数参数")
var countUint = flag.Uint("count-uint", 1, "无符号整数参数")
// 浮点数类型
var ratio = flag.Float64("ratio", 1.0, "浮点数参数")
// 布尔类型
var verbose = flag.Bool("verbose", false, "布尔参数")
// 时间类型
var timeout = flag.Duration("timeout", time.Second, "时间间隔参数")
// IP地址类型
var ip = flag.IP("ip", net.IPv4zero, "IP地址参数")
flag.Parse()
fmt.Printf("Name: %s\n", *name)
fmt.Printf("Count: %d\n", *count)
fmt.Printf("Ratio: %.2f\n", *ratio)
fmt.Printf("Verbose: %t\n", *verbose)
fmt.Printf("Timeout: %v\n", *timeout)
fmt.Printf("IP: %s\n", *ip)
}
2.1.2 绑定到现有变量
package main
import (
"fmt"
flag "github.com/spf13/pflag"
)
func main() {
// 声明变量
var (
host string
port int
debug bool
maxConns uint
)
// 绑定到现有变量
flag.StringVar(&host, "host", "localhost", "服务器主机")
flag.IntVar(&port, "port", 8080, "服务器端口")
flag.BoolVar(&debug, "debug", false, "调试模式")
flag.UintVar(&maxConns, "max-conns", 100, "最大连接数")
flag.Parse()
fmt.Printf("Host: %s, Port: %d, Debug: %t, MaxConns: %d\n",
host, port, debug, maxConns)
}
2.2 短选项支持
2.2.1 使用 P 后缀函数
package main
import (
"fmt"
flag "github.com/spf13/pflag"
)
func main() {
// 支持短选项的定义方式
var (
configFile = flag.StringP("config", "c", "config.json", "配置文件路径")
verbose = flag.BoolP("verbose", "v", false, "详细输出")
port = flag.IntP("port", "p", 8080, "服务端口")
help = flag.BoolP("help", "h", false, "显示帮助")
)
flag.Parse()
if *help {
flag.Usage()
return
}
fmt.Printf("Config: %s, Verbose: %t, Port: %d\n",
*configFile, *verbose, *port)
}
使用示例:
# 长选项
./app --config config.yaml --verbose --port 9090
# 短选项
./app -c config.yaml -v -p 9090
# 混合使用
./app -c config.yaml --verbose -p 9090
# 布尔短选项组合
./app -cvp 9090 # 等同于 -c -v -p 9090
2.3 切片类型支持
2.3.1 各种切片类型
package main
import (
"fmt"
flag "github.com/spf13/pflag"
)
func main() {
// 字符串切片
hosts := flag.StringSliceP("hosts", "H", []string{}, "主机列表")
// 整数切片
ports := flag.IntSlice("ports", []int{80, 443}, "端口列表")
// 布尔切片
flags := flag.BoolSlice("flags", []bool{true, false}, "标志列表")
// 64位整数切片
ids := flag.Int64Slice("ids", []int64{}, "ID列表")
flag.Parse()
fmt.Printf("Hosts: %v\n", *hosts)
fmt.Printf("Ports: %v\n", *ports)
fmt.Printf("Flags: %v\n", *flags)
fmt.Printf("IDs: %v\n", *ids)
}
使用示例:
./app --hosts host1,host2,host3 --ports 8080,9090 --ids 1,2,3
./app -H host1,host2 --ports 80,443,8080
2.3.2 多次指定参数
package main
import (
"fmt"
flag "github.com/spf13/pflag"
)
func main() {
var tags []string
// 自定义处理多次出现的参数
flag.StringArrayVar(&tags, "tag", []string{}, "标签(可多次指定)")
flag.Parse()
fmt.Printf("Tags: %v\n", tags)
}
使用示例:
./app --tag go --tag docker --tag kubernetes
# Tags: [go docker kubernetes]
2.4 高级功能
2.4.1 非默认值(NoOptDefVal)
package main
import (
"fmt"
flag "github.com/spf13/pflag"
)
func main() {
var verbose = flag.BoolP("verbose", "v", false, "详细输出模式")
// 设置无参数时的默认值
flag.Lookup("verbose").NoOptDefVal = "true"
flag.Parse()
fmt.Printf("Verbose: %t\n", *verbose)
}
使用示例:
./app -v # Verbose: true (因为设置了 NoOptDefVal)
./app -v=true # Verbose: true
./app -v=false # Verbose: false
2.4.2 参数验证
package main
import (
"errors"
"fmt"
"strconv"
flag "github.com/spf13/pflag"
)
// 自定义验证函数
func validatePort(port string) error {
p, err := strconv.Atoi(port)
if err != nil {
return errors.New("端口必须是数字")
}
if p < 1 || p > 65535 {
return errors.New("端口必须在 1-65535 范围内")
}
return nil
}
func main() {
var port string
flag.StringVar(&port, "port", "8080", "服务端口")
// 解析后验证
flag.Parse()
if err := validatePort(port); err != nil {
fmt.Printf("参数验证失败: %v\n", err)
return
}
fmt.Printf("端口: %s\n", port)
}
3. Pflag 高级特性
3.1 自定义类型
3.1.1 实现 Value 接口
package main
import (
"fmt"
"strings"
flag "github.com/spf13/pflag"
)
// 自定义字符串切片类型
type StringSlice struct {
values []string
}
func (s *StringSlice) String() string {
return strings.Join(s.values, ",")
}
func (s *StringSlice) Set(value string) error {
s.values = append(s.values, value)
return nil
}
func (s *StringSlice) Type() string {
return "stringSlice"
}
func (s *StringSlice) Get() interface{} {
return s.values
}
func main() {
var tags StringSlice
flag.Var(&tags, "tag", "标签(可多次指定)")
flag.Parse()
fmt.Printf("Tags: %v\n", tags.values)
}
3.1.2 枚举类型
package main
import (
"errors"
"fmt"
flag "github.com/spf13/pflag"
)
type LogLevel string
const (
Debug LogLevel = "debug"
Info LogLevel = "info"
Warn LogLevel = "warn"
Error LogLevel = "error"
)
func (l *LogLevel) String() string {
return string(*l)
}
func (l *LogLevel) Set(value string) error {
switch value {
case "debug", "info", "warn", "error":
*l = LogLevel(value)
return nil
default:
return errors.New("必须是 debug、info、warn 或 error")
}
}
func (l *LogLevel) Type() string {
return "logLevel"
}
func main() {
var level LogLevel = Info
flag.Var(&level, "log-level", "日志级别 (debug|info|warn|error)")
flag.Parse()
fmt.Printf("日志级别: %s\n", level)
}
3.2 标志分组和组织
3.2.1 使用 FlagSet 分组
package main
import (
"fmt"
flag "github.com/spf13/pflag"
"os"
)
func main() {
// 创建不同的 FlagSet
serverFlags := flag.NewFlagSet("server", flag.ContinueOnError)
clientFlags := flag.NewFlagSet("client", flag.ContinueOnError)
// 服务器相关标志
serverHost := serverFlags.String("host", "localhost", "服务器主机")
serverPort := serverFlags.Int("port", 8080, "服务器端口")
// 客户端相关标志
clientTimeout := clientFlags.Duration("timeout", time.Second*30, "客户端超时")
clientRetries := clientFlags.Int("retries", 3, "重试次数")
if len(os.Args) < 2 {
fmt.Println("期望 'server' 或 'client' 子命令")
os.Exit(1)
}
switch os.Args[1] {
case "server":
serverFlags.Parse(os.Args[2:])
fmt.Printf("启动服务器: %s:%d\n", *serverHost, *serverPort)
case "client":
clientFlags.Parse(os.Args[2:])
fmt.Printf("客户端配置: 超时=%v, 重试=%d\n", *clientTimeout, *clientRetries)
default:
fmt.Printf("未知命令: %s\n", os.Args[1])
os.Exit(1)
}
}
3.3 标志规范化
3.3.1 名称标准化
package main
import (
"fmt"
"strings"
flag "github.com/spf13/pflag"
)
func main() {
// 设置标准化函数
flag.CommandLine.SetNormalizeFunc(func(f *flag.FlagSet, name string) flag.NormalizedName {
// 将下划线转换为连字符
result := strings.ReplaceAll(name, "_", "-")
return flag.NormalizedName(result)
})
var configFile string
flag.StringVar(&configFile, "config_file", "", "配置文件路径")
flag.Parse()
fmt.Printf("配置文件: %s\n", configFile)
}
使用示例:
# 以下两种方式都有效
./app --config_file config.yaml
./app --config-file config.yaml
4. Pflag 在 Cobra 中的使用
4.1 Cobra 与 Pflag 集成基础
4.1.1 基本标志定义
package main
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "我的应用程序",
Long: "这是一个使用 Cobra 和 Pflag 的示例应用程序",
}
var serveCmd = &cobra.Command{
Use: "serve",
Short: "启动服务",
Run: func(cmd *cobra.Command, args []string) {
host, _ := cmd.Flags().GetString("host")
port, _ := cmd.Flags().GetInt("port")
verbose, _ := cmd.Flags().GetBool("verbose")
fmt.Printf("启动服务在 %s:%d, 详细模式: %t\n", host, port, verbose)
},
}
func init() {
// 为 serve 命令添加标志
serveCmd.Flags().StringP("host", "H", "localhost", "服务监听地址")
serveCmd.Flags().IntP("port", "p", 8080, "服务监听端口")
serveCmd.Flags().BoolP("verbose", "v", false, "详细输出")
rootCmd.AddCommand(serveCmd)
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
4.2 持久化标志与本地标志
4.2.1 标志作用域
package main
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
var (
configFile string
logLevel string
)
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "我的应用程序",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("配置文件: %s, 日志级别: %s\n", configFile, logLevel)
},
}
var serverCmd = &cobra.Command{
Use: "server",
Short: "服务器命令",
Run: func(cmd *cobra.Command, args []string) {
port, _ := cmd.Flags().GetInt("port")
fmt.Printf("启动服务器在端口: %d\n", port)
},
}
var clientCmd = &cobra.Command{
Use: "client",
Short: "客户端命令",
Run: func(cmd *cobra.Command, args []string) {
timeout, _ := cmd.Flags().GetDuration("timeout")
fmt.Printf("客户端超时: %v\n", timeout)
},
}
func init() {
// 持久化标志 - 所有子命令都可用
rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "config.yaml", "配置文件路径")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "日志级别")
// 服务器命令的本地标志
serverCmd.Flags().IntP("port", "p", 8080, "服务器端口")
// 客户端命令的本地标志
clientCmd.Flags().DurationP("timeout", "t", 30, "客户端超时时间(秒)")
rootCmd.AddCommand(serverCmd, clientCmd)
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
4.3 必需标志与验证
4.3.1 标志验证
package main
import (
"fmt"
"github.com/spf13/cobra"
"os"
"strconv"
)
var deployCmd = &cobra.Command{
Use: "deploy ENVIRONMENT",
Short: "部署应用到指定环境",
Args: cobra.ExactArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
// 验证必需标志
if image, _ := cmd.Flags().GetString("image"); image == "" {
return fmt.Errorf("--image 标志是必需的")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
environment := args[0]
image, _ := cmd.Flags().GetString("image")
replicas, _ := cmd.Flags().GetInt32("replicas")
fmt.Printf("部署 %s 到环境 %s, 副本数: %d\n", image, environment, replicas)
},
}
func init() {
deployCmd.Flags().StringP("image", "i", "", "容器镜像 (必需)")
deployCmd.Flags().Int32P("replicas", "r", 1, "副本数量")
// 标记为必需标志
deployCmd.MarkFlagRequired("image")
rootCmd.AddCommand(deployCmd)
}
4.4 高级 Cobra + Pflag 功能
4.4.1 标志分组和分类
package main
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
var complexCmd = &cobra.Command{
Use: "complex",
Short: "复杂命令示例",
Run: func(cmd *cobra.Command, args []string) {
dbHost, _ := cmd.Flags().GetString("db-host")
dbPort, _ := cmd.Flags().GetInt("db-port")
cacheHost, _ := cmd.Flags().GetString("cache-host")
cachePort, _ := cmd.Flags().GetInt("cache-port")
fmt.Printf("数据库: %s:%d\n", dbHost, dbPort)
fmt.Printf("缓存: %s:%d\n", cacheHost, cachePort)
},
}
func init() {
// 数据库相关标志
complexCmd.Flags().String("db-host", "localhost", "数据库主机")
complexCmd.Flags().Int("db-port", 5432, "数据库端口")
// 缓存相关标志
complexCmd.Flags().String("cache-host", "localhost", "缓存主机")
complexCmd.Flags().Int("cache-port", 6379, "缓存端口")
// 网络相关标志
complexCmd.Flags().StringP("bind", "b", "0.0.0.0", "绑定地址")
complexCmd.Flags().IntP("port", "p", 8080, "服务端口")
rootCmd.AddCommand(complexCmd)
}
4.4.2 配置绑定(与 Viper 集成)
package main
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
)
var configCmd = &cobra.Command{
Use: "config",
Short: "配置相关命令",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// 绑定标志到 Viper
viper.BindPFlags(cmd.Flags())
},
}
var showConfigCmd = &cobra.Command{
Use: "show",
Short: "显示配置",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("数据库主机: %s\n", viper.GetString("db.host"))
fmt.Printf("数据库端口: %d\n", viper.GetInt("db.port"))
fmt.Printf("调试模式: %t\n", viper.GetBool("debug"))
},
}
func init() {
// 定义配置标志
configCmd.PersistentFlags().String("db.host", "localhost", "数据库主机")
configCmd.PersistentFlags().Int("db.port", 5432, "数据库端口")
configCmd.PersistentFlags().Bool("debug", false, "调试模式")
configCmd.AddCommand(showConfigCmd)
rootCmd.AddCommand(configCmd)
}
5. 最佳实践和高级技巧
5.1 错误处理模式
package main
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
var safeCmd = &cobra.Command{
Use: "safe",
Short: "安全命令执行示例",
RunE: func(cmd *cobra.Command, args []string) error {
// 使用 RunE 返回错误
port, err := cmd.Flags().GetInt("port")
if err != nil {
return fmt.Errorf("获取端口失败: %w", err)
}
if port < 1 || port > 65535 {
return fmt.Errorf("端口 %d 超出范围 (1-65535)", port)
}
host, err := cmd.Flags().GetString("host")
if err != nil {
return fmt.Errorf("获取主机失败: %w", err)
}
fmt.Printf("连接到 %s:%d\n", host, port)
return nil
},
}
func init() {
safeCmd.Flags().StringP("host", "H", "localhost", "目标主机")
safeCmd.Flags().IntP("port", "p", 8080, "目标端口")
rootCmd.AddCommand(safeCmd)
}
5.2 自定义帮助模板
package main
import (
"github.com/spf13/cobra"
"os"
)
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "我的应用程序",
Long: `这是一个详细的应用程序描述。
可以在这里写多行描述,包括示例和使用说明。`,
}
func init() {
// 自定义帮助模板
rootCmd.SetHelpTemplate(`{{.Short}}
用法:
{{.UseLine}} [命令] [标志] [参数]
命令:
{{range .Commands}}{{if .IsAvailableCommand}} {{.Name | printf "%-12s"}} {{.Short}}
{{end}}{{end}}
标志:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}
使用 "{{.CommandPath}} [命令] --help" 查看命令的详细信息。
`)
// 添加示例命令
exampleCmd := &cobra.Command{
Use: "example",
Short: "示例命令",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
rootCmd.AddCommand(exampleCmd)
}
func main() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
5.3 性能优化技巧
package main
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"os"
)
// 使用 FlagSet 预解析常用标志
var globalFlags = pflag.NewFlagSet("global", pflag.ContinueOnError)
func init() {
// 预定义全局标志
globalFlags.StringP("config", "c", "", "配置文件路径")
globalFlags.BoolP("verbose", "v", false, "详细输出")
}
var optimizedCmd = &cobra.Command{
Use: "optimized",
Short: "性能优化示例",
PreRun: func(cmd *cobra.Command, args []string) {
// 快速解析常用标志
globalFlags.Parse(os.Args[1:])
},
Run: func(cmd *cobra.Command, args []string) {
if verbose, _ := globalFlags.GetBool("verbose"); verbose {
fmt.Println("详细模式启用")
}
// 正常解析其他标志
customFlag, _ := cmd.Flags().GetString("custom")
fmt.Printf("自定义标志: %s\n", customFlag)
},
}
func init() {
optimizedCmd.Flags().String("custom", "", "自定义标志")
rootCmd.AddCommand(optimizedCmd)
}
这个详细教程涵盖了 Pflag 的基础到高级用法,以及在 Cobra 中的集成方式。通过这些示例,你可以构建出功能丰富、用户友好的命令行应用程序。