flag.Parse()必须在定义所有flag后调用,否则参数无法解析;自定义flag类型须实现flag.Value接口(Set和String方法);非flag参数需用flag.Arg(i)或flag.Args()在Parse后获取;短长选项需分别注册。

flag.Parse() 必须在定义所有 flag 后调用,否则参数不会被解析
Go 的 flag 包是惰性绑定机制:变量声明(如 flag.String)只是注册参数,不立即读取值;真正解析发生在 flag.Parse() 执行时。如果在 flag.Parse() 之前访问 flag 变量,得到的是零值(比如空字符串、0、false),不是用户输入的值。
常见错误写法:
port := flag.String("port", "8080", "server port")
fmt.Println(*port) // 输出 "",不是 "8080" 或用户传的值
flag.Parse() // 太晚了,上面已经读过了
正确顺序:
- 先用
flag.String/flag.Int等注册所有 flag - 再调用
flag.Parse() - 最后读取指针解引用(
*port)或用flag.Lookup("port").Value.String()
自定义 flag 类型需实现 flag.Value 接口,不能只实现 String() 方法
很多初学者以为只要结构体有 String() 方法就能当 flag 用,其实不行。flag 要求类型必须满足 flag.Value 接口:Set(string) error 和 String() string 都要实现。缺少 Set,flag 就无法把命令行字符串赋值给该类型。
立即学习“go语言免费学习笔记(深入)”;
例如想支持逗号分隔的字符串切片:
type StringSlice []string
func (s *StringSlice) Set(value string) error {
*s = strings.Split(value, ",")
return nil
}
func (s *StringSlice) String() string {
return strings.Join(*s, ",")
}
// 使用:
var includes StringSlice
flag.Var(&includes, "include", "comma-separated list of files to include")
注意:flag.Var 传入的是指针(&includes),且 Set 方法必须为指针接收者,否则修改不会反映到原变量。
flag.Arg(i) 和 flag.Args() 用于获取非 flag 参数(即“位置参数”)
命令如 ./app -v config.yaml --timeout=30 中,config.yaml 是非 flag 参数(它前面没 - 或 --)。这类参数不会被 flag.String 捕获,得用 flag.Args() 获取完整切片,或用 flag.Arg(i) 按索引取单个。
典型使用场景:
- 主文件路径:
file := flag.Arg(0)(要求至少一个位置参数) - 多个输入文件:
files := flag.Args()[1:](跳过第一个) - 注意:必须在
flag.Parse()之后调用,否则返回空切片 - 如果位置参数有固定语义(如
cp src dst),建议用flag.NArg()校验数量,避免静默失败
短选项(-h)和长选项(--help)默认不自动关联,需手动设置别名或用 flag.BoolVar
flag 包本身不认为 -h 和 --help 是同一参数。如果你写 flag.Bool("help", false, "..."),那只有 --help 有效;-h 会报错 flag provided but not defined。
两种常用解法:
- 用
flag.BoolVar绑定同一个变量,分别注册:flag.BoolVar(&help, "h", false, "show help")和flag.BoolVar(&help, "help", false, "show help") - 更简洁:直接用
flag.Bool注册短名,再用flag.Lookup("h").Usage = "show help"补充说明(但依然要注册两次) - 第三方库如
github.com/spf13/pflag原生支持-h/--help自动映射,适合复杂 CLI
另外,flag.PrintDefaults() 不会自动触发退出,需配合 os.Exit(0) 手动终止程序。









