flag.string 返回指针因需在解析时直接修改变量内存地址内容;传值无法生效,解引用用*msg;子命令需手动切分os.args并提前提取,在flag.parse前初始化独立flagset。

为什么 flag.String 返回的是指针而不是值
因为 flag 包需要在解析命令行时直接修改变量内存地址里的内容,如果传值,函数内部改了也没用。你声明一个 string 变量,然后用 flag.String 绑定它,本质是让 flag 在解析完后把结果写进那个变量的地址里。
常见错误:写成 msg := flag.String("msg", "", "备忘内容"),然后直接用 msg 当字符串拼接或打印——这会 panic,因为 msg 是 *string,得解引用:*msg。
- 正确用法:
msg := flag.String("msg", "", "备忘内容"); fmt.Println(*msg) - 如果想避免解引用,用
flag.StringVar配合已有变量:var msg string; flag.StringVar(&msg, "msg", "", "备忘内容") -
flag.String和flag.StringVar行为一致,只是接口不同;后者更易读、少出错
如何支持「添加」和「列出」两个子命令(类似 memo add -m "xxx")
Go 标准库 flag 本身不支持子命令,硬套会导致参数解析混乱(比如 memo add -m "x" 中的 -m 被顶层 flag 忽略)。必须手动切分 os.Args,提前捕获第一个非 flag 参数作为子命令名。
关键点:在调用 flag.Parse() 前,先检查 os.Args 长度,并提取子命令;之后再初始化对应子命令的 flag 集合。
立即学习“go语言免费学习笔记(深入)”;
- 不要对整个
os.Args调一次flag.Parse()就完事 - 子命令的 flag 应该各自独立定义,例如
addCmd := flag.NewFlagSet("add", flag.ContinueOnError) - 注意
flag.ContinueOnError:避免子命令解析失败时直接退出程序 - 示例片段:
if len(os.Args)
flag.Parse() 之后还能再解析别的参数吗
不能。每个 flag.FlagSet 实例只能调用一次 Parse(),重复调用会 panic:flag provided but not defined: -xxx。这是设计使然——Parse() 会消费掉所有匹配的参数,并把剩余非 flag 参数存进 FlagSet.Args()。
如果你需要多次解析(比如主命令 + 子命令),必须用多个独立的 flag.FlagSet 实例,各自调用一次 Parse()。
- 顶层 flag 用默认
flag.CommandLine,子命令用flag.NewFlagSet - 别试图复用同一个 FlagSet 解析多段参数
- 如果忘了清空或重置,后续 Parse 会报错,错误信息里出现的
-xxx就是上次没被识别的残留参数
备忘录数据存哪?用 JSON 文件时要注意什么
命令行工具默认没状态,每次运行都是新进程,所以必须持久化到文件。用 JSON 是合理选择,但 Go 的 json.Marshal 默认导出首字母大写的字段,小写字段会被忽略——这意味着结构体字段必须大写且加 json: tag,否则写进去的是空对象。
另一个坑是并发:如果用户快速连发两条 memo add,可能两个进程同时读-改-写同一文件,导致丢失数据。简单 CLI 工具可暂不处理并发,但至少要加 os.O_APPEND 或全量重写时用临时文件 + os.Rename 避免中间态损坏。
- 结构体字段必须导出:
type Note struct { Text string `json:"text"`; Time string `json:"time"` } - 写文件前先
ioutil.WriteFile到临时路径,再os.Rename替换原文件,保证原子性 - 读文件时检查
os.IsNotExist(err),首次运行要容忍空文件或不存在 - 别用
flag.String接收文件路径然后直接os.Open——用户可能输错路径,记得加错误处理










