一个增强版的 Go 命令行参数解析库,兼容标准库 flag 的用法,并在此基础上扩展了配置文件、环境变量、隐藏参数、子命令、别名、字符串/数值切片等能力。
- 多来源优先级:命令行参数 > 环境变量 > 配置文件 > 默认值
- 🌟 统一配置入口
-c:Parse()自动注册隐藏参数-c,传入配置文件后命令行/环境变量/配置文件按优先级一次性合并,无需手动调用 viper - 配置文件集成:基于 viper,支持 JSON / TOML / YAML 等格式,参数变更可回写配置
- 隐藏参数:支持注册不在
--help中显示的参数,但仍可被解析 - 子命令 & 别名:支持注册子命令(如
git风格),并为参数/子命令设置别名(如-v↔--verbose) - 位置参数与选项可交错:
FlagSet.Parse支持选项写在位置参数前后;布尔选项不会吞掉路径,--后内容始终按字面位置参数保留。顶层子命令分发可使用ParseStandard在首个位置参数处停止 - 切片类型:内置
Strings / Ints / Int64s / Uints / Uint64s等切片类型,逗号分隔解析、多次传参自动累加 - 反射通用入口
Var():一个 API 覆盖所有基础类型和切片类型 - 彩色帮助输出:内置 ANSI 彩色打印,
PrintAll以表格形式列出全部参数
go get github.com/lfhy/flagpackage main
import (
"fmt"
"github.com/lfhy/flag"
)
var (
Port int
Host string
Tags []string
Debug bool
)
func init() {
flag.IntVar(&Port, "port", 8080, "服务端口")
flag.StringVar(&Host, "host", "127.0.0.1", "监听地址")
flag.StringsVar(&Tags, "tags", "go,linux", "标签(逗号分隔)")
flag.BoolVar(&Debug, "debug", false, "调试模式")
flag.Parse()
}
func main() {
fmt.Printf("Port=%d Host=%s Tags=%v Debug=%v\n", Port, Host, Tags, Debug)
}运行:
$ go run main.go -port 9000 -tags a,b -tags c
Port=9000 Host=127.0.0.1 Tags=[a b c] Debug=false包级别提供了与标准库一致风格的 XxxVar / Xxx 函数;也可通过 FlagSet 在子命令中独立使用。
| 方式 | 说明 |
|---|---|
flag.IntVar(p, "n", 1, "...") |
基础类型,仅命令行 + 默认值 |
flag.IntEnvVar(p, "n", "APP_N", 1, "...") |
同时绑定环境变量 |
flag.IntConfigVar(p, "n", "server", "port", 1, "...") |
同时绑定配置文件 |
flag.IntFullVar(p, "n", "server", "port", "APP_N", 1, "...") |
全部来源 |
flag.Int("n", 1, "...") |
返回 *int,无需预先定义变量 |
函数名加 Hidden(如 IntHiddenVar) |
注册为隐藏参数 |
所有类型(Bool / String / Int / Int64 / Uint / Uint64 / Float64 / Duration 以及对应的切片 Strings/Ints/... )均遵循同一套命名规则。
flag.New(或 fs.New)可链式设置别名、默认值、配置键、环境变量和帮助文字。以类型方法结尾会立即注册参数并返回指针;Parse 会把最终值写入该指针:
follow := flag.New("follow").Alias("f").Default(false).Config("title.key").Env("FOLLOW").Bool()
if err := flag.Parse(); err != nil { panic(err) }
fmt.Println(*follow) // 根据 -follow/-f、FOLLOW、配置文件或默认值确定.Config("title.key") 映射配置文件的 [title] 节和 key 键;未设置 ConfigFile 时,要读取文件需在命令行传 -c config.toml(或通过 fs.SetConfigFlagName("conf") 改用 -conf)。环境变量是输入来源,解析不会将值写回环境变量。优先级为 命令行 > 环境变量 > 配置文件 > 默认值。
已有变量可用 .Var(&data) 绑定;它会立即注册参数,不返回指针:
var data bool
flag.New("enabled").Default(false).Var(&data)
if err := flag.Parse(); err != nil { panic(err) }
fmt.Println(data)也可以不调用类型方法或 .Var,直接保留定义并在解析后读取:
fs := flag.NewFlagSet("app", flag.ContinueOnError)
definition := fs.New("follow").Default(false).Env("FOLLOW")
count := fs.New("count").Default("3") // 未以类型方法结尾:默认值保留为文本
if err := fs.Parse([]string{"-follow", "-count=5"}); err != nil { panic(err) }
fmt.Println(definition.Value().Bool()) // true(值类型为 bool,不是 *bool)
fmt.Println(count.Value().Int(), count.Value().String()) // 5 5无类型结尾的定义由 Parse 注册为文本值,.Value().Bool()/Int()/Int64()/String() 等方法在读取时转换;文本不能转换时,布尔值与数值读取方法会 panic,而 .Value().String() 原样返回文本。类型结尾的 .Bool()/Int()/Int64()/String() 等方法则返回对应类型的指针(*bool、*int、*int64、*string 等)。
FlagSet 会保留已解析的参数状态;如果要用另一组环境变量或配置独立解析,请新建 FlagSet。
切片类型支持逗号分隔解析,多次传参会累加(与 pflag 的 StringSlice 行为一致):
var ids []int
flag.IntsVar(&ids, "ids", "1,2,3", "ID 列表")
// -ids=4,5 -ids=6 → [1 2 3 4 5 6]支持:StringsVar / IntsVar / Int64sVar / UintsVar / Uint64sVar。
Var() 接收一个 FlagVar 结构体,通过反射自动识别基础类型和切片类型,无需为每种类型单独调用 API:
var (
name string
age int
tags []string
ids []int64
)
flag.Var(&flag.FlagVar{Value: &name, Name: "name", Env: "APP_NAME", DefaultValue: "default"})
flag.Var(&flag.FlagVar{Value: &age, Name: "age", DefaultValue: 18})
flag.Var(&flag.FlagVar{Value: &tags, Name: "tags", DefaultValue: "a,b,c"})
flag.Var(&flag.FlagVar{Value: &ids, Name: "ids", DefaultValue: []int64{1, 2}})Var() 支持的类型:bool / string / int* / uint* / float64 / time.Duration 以及对应的切片。
// 绑定环境变量:未在命令行指定时从环境变量读取
flag.StringEnvVar(&host, "host", "APP_HOST", "0.0.0.0", "监听地址")
// 绑定配置文件:从 title.key 读取
flag.IntConfigVar(&port, "port", "server", "port", 8080, "服务端口")
// 读取/回写配置
cfg := flag.GetConfig()
cfg.WriteToConfig("server", "port", 9000)优先级:命令行 > 环境变量 > 配置文件 > 默认值。
这是本库区别于标准库 flag 的核心特性。Parse() 时会自动注册一个隐藏参数 -c;未设置 ConfigFile 时,需传入配置文件路径。命令行参数、配置文件、环境变量会在一次解析中按优先级统一合并,无需手动调用 viper。
// app.go
var (
Host string
Port int
)
func init() {
flag.StringEnvVar(&Host, "host", "APP_HOST", "127.0.0.1", "监听地址")
flag.IntConfigVar(&Port, "port", "server", "port", 8080, "服务端口")
flag.Parse()
}假设有配置文件 config.toml:
[server]
port = 9000未设置 ConfigFile 时,三种来源合并:
# 1. 完全使用配置文件
$ go run app.go -c config.toml
# Host=127.0.0.1(默认值) Port=9000(来自配置文件)
# 2. 命令行覆盖配置文件
$ go run app.go -c config.toml -port 8888
# Port=8888(命令行 > 配置文件)
# 3. 环境变量也参与(未在命令行/配置中出现时生效)
$ APP_HOST=0.0.0.0 go run app.go -c config.toml
# Host=0.0.0.0(环境变量 > 默认值)设置默认配置文件后,不传 -c 也会自动读取存在的文件;默认文件不存在时跳过读取,显式指定的文件不存在或文件无法解析时返回错误:
flag.ConfigFile("config.toml") // 在 flag.Parse() 前调用;绑定的配置键仍通过 Config/IntConfigVar 等定义go run app.go 使用 config.toml;go run app.go -c other.toml 则使用显式指定的文件。命令行参数 > 环境变量 > 配置文件 > 默认值的优先级不变。配置路径参数还可链式命名(以下为分别选用的写法):
flag.ConfigFile("config.toml").Name("config") // 仅 -config,不再提供 -c
flag.ConfigFile("config.toml").Alias("config") // 同时接受 -c 和 -config
fs := flag.NewFlagSet("app", flag.ContinueOnError)
fs.ConfigFile("config.toml").Alias("config") // 仅作用于此 FlagSet要点:
-c是自动注册的隐藏参数,无需手动定义;未设置ConfigFile且未传路径时跳过文件解析- 旧接口
fs.SetConfigFlagName("conf")仍可自定义参数名(FlagSet 级别,互不影响,默认"c");若也调用.Name(...),解析前最后一次命名生效 - 配置文件按
ConfigTitle.ConfigKey(即IntConfigVar第二、三个参数)的节/键读取 - 可通过
flag.DefaultConfigFlagName修改默认标志名(默认"c") - 解析完成后用
flag.GetConfig()获取*Config,支持读写回文件
flag.String("verbose", "false", "详细输出")
flag.Alias("verbose", "v") // -v 等价于 --verbose链式定义可用 .Alias("v");使用 flag.Var 时也可设置 FlagVar.Aliases,例如 flag.Var(&flag.FlagVar{Value: &verbose, Name: "verbose", Aliases: []string{"v"}})。
flag.StringHiddenVar(&token, "token", "", "内部鉴权 Token")
// 不会出现在 --help 输出中,但 -token=xxx 仍然生效实现 Cmd 接口(Name / Init / Run / Help)后注册即可:
type ServeCmd struct { *flag.FlagSet }
func (*ServeCmd) Name() string { return "serve" }
func (s *ServeCmd) Init(args ...string) error {
s.FlagSet = flag.NewFlagSet("serve", flag.ContinueOnError)
// ... 定义子命令参数
return s.Parse(args)
}
func (*ServeCmd) Run(args ...string) error { /* ... */ return nil }
func (*ServeCmd) Help() string { return "启动服务" }
flag.RegisterCommand(&ServeCmd{})
flag.Parse()每个类型在每个层级(FlagSet 方法 / ArgsFlag 方法 / 包级函数)都提供 8 个函数:
XxxFullVar / XxxConfigVar / XxxEnvVar / XxxVar // 写入已定义变量
XxxFull / XxxConfig / XxxEnv / Xxx // 返回 *T
加上对应 8 个 Hidden 变体。覆盖类型:
| 基础类型 | 切片类型 |
|---|---|
| Bool / String | Strings |
| Int / Int64 | Ints / Int64s |
| Uint / Uint64 | Uints / Uint64s |
| Float64 | — |
| Duration | — |
go test ./...MIT