flag.parse()必须在所有flag定义之后调用,否则未注册的flag无法被识别;stringvar比string更安全,避免nil指针解引用;-h和--help需分别注册;子命令需用独立flagset解析。

flag.Parse() 必须在所有 flag 定义之后调用,否则参数永远拿不到用户输入的值——这是 90% 的新手卡住的地方。
flag.Parse() 调用顺序错不得
它不是“启动解析器”,而是“把 os.Args[1:] 拿去匹配当前已注册的所有 flag”。定义在它之后的 flag 根本不会被识别,flag.Parse() 执行时只看到前面注册过的那些。
- 错误写法:
flag.Parse()放在第一行,再调flag.String("port", "8080", "")→ 用户传-port 3000,打印出来还是"8080" - panic 场景:
flag.String("x", "", "")写在flag.Parse()后面,然后执行go run main.go -x a→ 报错"flag provided but not defined" - 正确节奏:所有
flag.String()、flag.IntVar()等统一写在main()开头;紧接着一行flag.Parse();之后才开始用变量
String 和 StringVar 到底选哪个
区别不在功能,在于谁持有内存地址、谁容易出错。别被“返回指针”吓住,关键是生命周期和可读性。
-
flag.String("name", "a", "")返回*string,必须解引用(*name)才能拿到值;但若在flag.Parse()前就解引用,会 panic:invalid memory address or nil pointer dereference -
flag.StringVar(&name, "name", "a", "")直接往你声明好的var name string里写,后续直接用name,无指针管理负担 - 结构体字段初始化、配置 struct、测试 mock 场景,必须用
StringVar;脚本类工具可酌情用String,但仍推荐统一用StringVar避免混淆
怎么让 -h 和 --help 都生效
Go flag 不做自动映射,-h 和 --help 是两个完全独立的 flag,不共享变量、不共用逻辑。
- 不能只注册
flag.Bool("help", false, "")就指望-h也管用 - 必须显式注册两个:
flag.BoolVar(&help, "h", false, "show help")和flag.BoolVar(&help, "help", false, "") - 注册完立刻检查:
if help { printUsage(); os.Exit(0) };别等后面业务逻辑跑完才处理,否则 help 出来得太晚 - 注意:
flag.Usage可重写,但默认触发方式是 panic 式退出,-h不会自动调它,得手动拦截
子命令(如 serve / migrate)没法靠 flag.CommandLine 解决
原生 flag 包不支持子命令。用 flag.CommandLine.Parse() 解析 myapp serve -p 3000,serve 会被当成未知 flag 报错。
- 正确做法:先调一次
flag.Parse()解析全局选项(比如--version);再用flag.Args()拿剩余参数 - 取
flag.Args()[0]判断子命令名;创建独立FlagSet:serveCmd := flag.NewFlagSet("serve", flag.ContinueOnError) - 对子命令参数调
serveCmd.Parse(os.Args[2:])(索引从 2 开始!),而不是再调flag.Parse() - 每个
FlagSet都要单独调自己的.Parse(),混用会导致状态污染
最易被忽略的是:flag 的注册时机和变量生命周期绑定极紧,flag.String 返回的指针在 Parse 前是 nil,而 StringVar 的目标变量必须在 Parse 时仍有效——局部变量若在函数内声明又提前 return,也可能踩空指针。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











