
flag.Parse() 必须在所有 flag.Xxx() 调用之后执行
很多新手一上来就写 flag.Parse(),结果所有标志都拿不到值——因为 flag.Parse() 只会解析此前已注册的标志。它不扫描全局变量,也不“自动发现”你定义的 flag.String() 或 flag.Int(),只处理它见过的那些。
常见错误现象:flag.String("port", "8080", "") 返回的指针始终指向默认值,flag.Args() 却有值;或者程序直接 panic:“flag provided but not defined”。
- 先调用
flag.String()、flag.Bool()等注册标志,再调用flag.Parse() - 不要把
flag.Parse()放在 init() 里(除非你 100% 确保所有注册已完成) - 如果用了
flag.CommandLine = flag.NewFlagSet(...),记得对新 set 调用它的Parse(),而不是全局的flag.Parse()
短选项(-h)和长选项(--help)默认不共存
Go 的 flag 包默认只认长选项(--help),-h 不会被识别——除非你显式注册它。这不是 bug,是设计选择:它不自动映射缩写,避免歧义(比如 -v 和 --verbose 是否等价,得你说了算)。
使用场景:写一个用户友好的 CLI,既要支持 ./cli -h,也要支持 ./cli --help。
立即学习“go语言免费学习笔记(深入)”;
- 用两个独立调用分别注册:
flag.Bool("h", false, "show help")和flag.Bool("help", false, "show help") - 注意:它们共享同一个底层变量,所以检查时用任一都行,但别漏掉其中一个的判断逻辑
- 如果想统一处理,建议注册后立刻检查:
if *helpFlag || *hFlag { printUsage(); os.Exit(0) }
flag.String() 返回的是 **string,不是 string
flag.String() 返回的是 *string(指向字符串的指针),不是值本身。直接打印或比较容易出错,尤其在结构体初始化或 map 赋值时。
常见错误现象:把 flag.String("name", "", "") 的返回值直接塞进 struct 字段,运行时报 panic:“invalid memory address”,因为指针可能为 nil(比如该 flag 没被传入,且没设默认值)。
- 安全做法:解引用前先确认是否非 nil,或统一用
*flag.String(...)获取默认值(但要注意:未传参时仍取默认值,不是空指针) - 更推荐方式:用变量接收,再传给
flag.StringVar(),避免指针管理负担 - 示例:
var name string flag.StringVar(&name, "name", "default", "user name") // 后续直接用 name,不用解引用
子命令场景下 flag.Parse() 无法自动切分参数
像 git commit -m "xxx" 这类带子命令的 CLI,flag 包本身不支持嵌套解析。它把整个命令行当平铺参数处理,flag.Parse() 会把 commit 当作未知 flag 报错,除非你手动跳过它。
性能 / 兼容性影响:自己切分 os.Args 简单直接,但失去 flag 对子命令各自 help、类型校验的支持;用第三方库(如 spf13/cobra)则更重,但健壮。
- 轻量方案:在
main()开头手动提取子命令名,用flag.Args()[0]判断,然后重新构造子命令的os.Args并调用新flag.FlagSet.Parse() - 必须重置
flag.CommandLine或用flag.NewFlagSet(),否则全局 flag 会污染子命令 - 注意:子命令的 help 文本不会自动包含在父命令中,需自行组织
flag.Parse(),或者误用全局 flag 导致参数串味。










