mcpField
G
go-cli

go-cli

github.com/eduardo-sl开发8395 次安装2 周前更新
代码仓库

关于 go-cli

面向 Go 开发者的命令行程序设计技能参考,适用于构建可测试、服务于用户和脚本的工具。内容涵盖标准流、参数、退出码、取消操作、测试、版本信息和 Shell 补全。

主要功能

  1. 01可测试的 run 函数结构
  2. 02stdout 与 stderr 约定
  3. 03标志、参数和退出码
  4. 04信号触发上下文取消
  5. 05面向人与机器的输出
  6. 06测试、版本信息与补全

适用场景

  1. 01设计可直接进行单元测试的 CLI
  2. 02将管道输出与诊断信息分开
  3. 03使命令响应 Ctrl+C 取消
  4. 04验证 CLI 行为和退出码

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-cli

安装后在 Claude Code 中输入/go-cli确认已加载。

附带文件references/testing-and-versioning.md

详细介绍

Go 命令行设计

优秀的命令行工具应当像一个守规矩的 Unix 公民:优先使用标志,而不是魔法行为;数据输出到 stdout,诊断信息输出到 stderr;退出码应当让脚本可以信赖;按下 Ctrl+C 时,程序确实会停止。

详细参考资料,按需加载:

  • references/testing-and-versioning.md — main 中的退出码映射、黄金文件和 testscript 测试、从构建信息读取版本、自动补全。

只有在下方摘要不够时,才阅读参考文件。

1. 结构:可测试的 main

func main() {
    ctx, stop := signal.NotifyContext(context.Background(),
        os.Interrupt, syscall.SIGTERM)
    defer stop()

    if err := run(ctx, os.Args[1:], os.Stdin, os.Stdout, os.Stderr); err != nil {
        fmt.Fprintln(os.Stderr, "error:", err)
        os.Exit(1)
    }
}

func run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) error {
    fs := flag.NewFlagSet("mytool", flag.ContinueOnError)
    fs.SetOutput(stderr)
    verbose := fs.Bool("v", false, "verbose output")
    out := fs.String("o", "-", "output file (- for stdout)")
    if err := fs.Parse(args); err != nil {
        return err
    }
    // ...
    _ = verbose
    _ = out
    return nil
}
  • signal.NotifyContext 会让 Ctrl+C 取消上下文——每个耗时操作都接收 ctx,并能干净地停止。上下文结束后调用一次 stop(),这样再次按下 Ctrl+C 就能立即终止卡住的程序。
  • run 接收参数和流——测试可直接调用它,并使用 strings.Reader/bytes.Buffer,无需启动子进程。
  • os.Exit 只在 main 中使用(它会跳过延迟调用)。

2. stdout 与 stderr

  • stdout:程序的输出——数据、结果,也就是管道传输的内容。
  • stderr:日志、进度、警告、用法错误。
  • --json 或检测到管道(!term.IsTerminal(int(os.Stdout.Fd())))时,应禁用装饰效果,绝不能改变数据。
// ✅ Good — result to stdout, progress to stderr
fmt.Fprintf(stderr, "processed %d files\n", n)
fmt.Fprintln(stdout, result)

// ❌ Bad — mixing both into stdout breaks every pipe
fmt.Printf("processing...\ndone: %s\n", result)

3. 退出码

Code含义
0成功
1通用运行时错误
2用法错误(标志或参数错误)—— flag 包的约定
>2工具特有且有文档说明的含义(例如 grep 的 1 表示没有匹配项)

在一个地方(main)将错误映射到退出码,不要在各处散布 os.Exit 调用。使用 flag.ContinueOnError 时,-h 会返回 flag.ErrHelp——对此应以 0 退出;并将解析错误包装为用法错误类型,以便 main 以 2 退出。按下 Ctrl+C 后以 130 退出。如果脚本需要根据不同失败类型采取不同操作,则定义哨兵错误并进行转换:errors.Is(err, ErrNoMatch) → 1。

4. 标志和参数

  • 选项使用标志,主要操作对象使用位置参数:mytool -v convert input.yaml,而不是 mytool --input=input.yaml。
  • 每个标志都应有用法说明;-h/-help 输出是主要的用户体验。
  • 文件参数接受 -,表示“stdin/stdout”。
  • 默认值必须安全:破坏性行为应由显式标志(--force)启用,绝不能默认启用。
  • 从环境变量或文件读取密钥,绝不要通过标志传入(ps 会泄露 argv)。

5. 子命令

标准库足以应对少量命令:

switch fs.Arg(0) {
case "serve":
    return runServe(ctx, fs.Args()[1:], stdout, stderr)
case "migrate":
    return runMigrate(ctx, fs.Args()[1:], stdout, stderr)
default:
    fmt.Fprintln(stderr, usage)
    return fmt.Errorf("unknown command %q", fs.Arg(0))
}

当需要嵌套命令、自动生成帮助和自动补全,以及大量标志时,采用 Cobra——它的结构足以抵消引入依赖的代价:

var rootCmd = &cobra.Command{Use: "mytool", SilenceUsage: true}

var serveCmd = &cobra.Command{
    Use:   "serve",
    Short: "Start the server",
    RunE: func(cmd *cobra.Command, args []string) error {
        return serve(cmd.Context(), addr) // RunE returns errors; no os.Exit
    },
}

func init() {
    serveCmd.Flags().StringVar(&addr, "addr", ":8080", "listen address")
    rootCmd.AddCommand(serveCmd)
}

Cobra 使用规则:始终使用 RunE(绝不组合使用 Run 和 os.Exit),设置 SilenceUsage: true,避免运行时错误时输出帮助信息,并向下传递 cmd.Context()。只有在确实需要分层配置(标志 > 环境变量 > 文件)时才添加 Viper——对大多数工具来说,flag + os.Getenv 已经足够。

6. 面向用户和机器的输出

  • 提供 --json 标志供机器读取;默认使用表格或文本,面向用户。
  • stdout 不是终端,或设置了 NO_COLOR 时,绝不输出 ANSI 颜色。
  • 进度条和加载动画输出到 stderr,且仅在终端中显示。

7. 测试

使用缓冲区直接测试 run,并将 stdout 与黄金文件进行比较;使用 -update 重新生成黄金文件。端到端行为——真实二进制、退出码、stdin——使用 testscript(.txtar 脚本)测试,这是 Go 命令自身测试所用的格式。

8. 版本信息

mytool version 通过 debug.ReadBuildInfo() 读取版本信息:从 Go 1.24 开始,主模块的版本来自 VCS 标签,vcs.revision 来自构建设置。只有在没有 VCS 元数据的情况下构建时,才回退到 -ldflags "-X main.version=..."。

9. Shell 自动补全

Cobra 可生成 completion bash|zsh|fish|powershell;添加 ValidArgsFunction 以支持动态值。不使用 Cobra 时,随发行版提供静态补全脚本。

第 3 节和第 7–9 节的示例见:references/testing-and-versioning.md。

验证清单

  1. 使用 run(ctx, args, stdin, stdout, stderr) 模式——无需子进程即可测试逻辑
  2. 接入 signal.NotifyContext;长时间运行的操作应遵循 ctx 取消信号
  3. 数据写入 stdout,诊断信息写入 stderr——通过管道验证
  4. 退出码:成功为 0,用法错误为 2,其他情况使用文档说明的退出码;os.Exit 仅在 main 中使用
  5. 每个标志都有用法说明;检查过 -h 的输出
  6. 接受 - 作为读取 stdin 或写入 stdout 的文件参数
  7. 破坏性操作必须通过显式标志启用
  8. 不通过 argv 传递机密信息
  9. 若使用 Cobra:所有位置都使用 RunE,设置 SilenceUsage,并传递 context
  10. 在非 TTY 环境和 NO_COLOR 设置下禁用颜色和加载动画
  11. -h 以状态码 0 退出,用法错误以状态码 2 退出,Ctrl+C 以状态码 130 退出
  12. version 输出来自构建信息;使用 golden 或 testscript 测试覆盖 CLI 的所有功能

评论

支持 Markdown