新项目应选 cobra 而非原生 flag;flag 仅适用于单命令极简脚本,cobra 提供命令树、自动 help、bash 补全等完整 cli 支持,且通过 persistentflags 与 flags 合理划分作用域,避免参数丢失。

用 flag 还是 cobra?选错会影响整个 CLI 结构
直接说结论:新项目别碰原生 flag,它只适合单命令、无子命令、参数极少的脚本。一旦要支持 task add、task list --done、task delete 12 这种层级结构,flag 就得自己 parse os.Args、手动分发、重复写 help 逻辑——不是不能做,是很快会写出一堆脆弱的字符串切片和 if 判断。
cobra 是事实标准,它把命令树、参数绑定、自动 help/usage、bash 补全都封装好了。你只需要定义「命令是什么」「它接受哪些 flag」「执行时调什么函数」。
- 安装:
go get -u github.com/spf13/cobra/cobra,然后用cobra init和cobra add生成骨架 - 每个子命令对应一个
*cobra.Command实例,RunE字段放实际逻辑(返回error便于统一错误处理) - 别在
Run里直接写业务逻辑——用RunE,这样出错能自然透出到顶层,cobra会帮你打印错误并退出非零状态码
cobra 的 PersistentFlags 和普通 Flags 怎么分?搞混就丢参数
常见现象:加了 --verbose,但在 task list 里取不到值;或者 task add --due tomorrow 报错说 unknown flag: --due。根本原因是没搞清 flag 作用域。
RootCmd.PersistentFlags() 添加的 flag 对所有子命令可见(比如 --config 或 --db-path),而 subCmd.Flags() 只属于该命令自身(比如 add 命令的 --due、--priority)。
立即学习“go语言免费学习笔记(深入)”;
- 全局配置类 flag(如
--data-dir)必须挂到RootCmd的PersistentFlags上,并在initConfig()里提前读取 - 每个子命令自己的 flag 必须在
cobra.AddCommand()之前调用其Flags().String()等方法注册 - 别在
RunE里用cmd.Flags().GetString()手动取值——先用var taskDue string定义变量,再用cmd.Flags().StringVar(&taskDue, "due", "", "due date")绑定,更安全且支持默认值
任务数据存哪?用 JSON 文件比 SQLite 更轻量也更易调试
CLI 工具初期不需要事务、并发锁或复杂查询。用 encoding/json 存到 $HOME/.taskdb.json 是最务实的选择:读写快、格式可读、git 友好、出问题能直接用 cat 或 jq 查看。
SQLite 虽然“正规”,但引入 cgo、需要建表、迁移麻烦、文件损坏后难恢复——对一个本地任务管理器来说,纯属过载。
- 定义结构体时字段名首字母大写(导出),并加上
json:"id"tag,否则序列化为空对象 - 读文件前先
os.Stat检查是否存在,不存在就用空 slice 初始化,别让json.Unmarshal遇到 nil slice panic - 每次写入前先
os.WriteFile到临时文件(如.taskdb.json.tmp),成功后再os.Rename,避免程序崩溃导致数据截断
怎么让 task list --done 和 task list --pending 不打架?用互斥 flag + 自定义验证
用户可能同时输 task list --done --pending,这时候你不拦着,逻辑就矛盾了。原生 cobra 不提供 flag 互斥机制,得自己加校验。
别指望靠文档或 help 提示来防止误用——用户一定会试,而且会截图发到群里问“为啥不报错却没输出”。必须在运行时拦截。
- 在
listCmd.RunE开头检查:done, _ := cmd.Flags().GetBool("done")和pending, _ := cmd.Flags().GetBool("pending") - 如果
done && pending,直接return fmt.Errorf("cannot specify both --done and --pending") - 同理,
--limit和--all也建议互斥,避免用户以为--limit 5 --all会忽略 limit
真正麻烦的从来不是写功能,而是预判用户怎么错着用它。比如时间解析("tomorrow"、"next Friday")、ID 不存在时的提示语气、Windows 下路径分隔符处理——这些细节堆起来,才是 CLI 是否“顺手”的分水岭。










