go-cli について
Go 開発者向けのコマンドライン設計スキルです。ユーザーとスクリプトの双方で扱いやすい、テスト可能なツールを作るため、標準ストリーム、フラグ、終了コード、キャンセル、テスト、バージョン表示、シェル補完を扱います。
主な機能
- 01テスト可能な run 関数の構成
- 02stdout と stderr の使い分け
- 03フラグ、引数、終了コード
- 04シグナルによるコンテキストキャンセル
- 05人と機械向けの出力
- 06テスト、バージョン情報、補完
活用シーン
- 01直接ユニットテストできる CLI の構成
- 02パイプ出力と診断情報の分離
- 03Ctrl+C によるコマンドのキャンセル
- 04CLI の動作と終了コードの検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ eduardo-sl/ go-agent-skills --skill go-cliインストール後、Claude Code で/go-cliを入力して読み込まれたか確認します。
同梱ファイルreferences/testing-and-versioning.md
詳細
Go CLI 設計
優れた CLI は、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()を呼び出し、2 回目の Ctrl+C で停止しないプログラムをすぐに終了させます。runは引数とストリームを受け取ります。テストではstrings.Reader/bytes.Bufferを使って直接呼び出せるため、サブプロセスは必要ありません。os.Exitを使うのはmainだけにします(os.Exitは defer を実行しません)。
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. 終了コード
| コード | 意味 |
|---|---|
| 0 | 成功 |
| 1 | 一般的な実行時エラー |
| 2 | 使用方法のエラー(フラグ/引数の誤り)— flag パッケージの慣例 |
| >2 | ツール固有の、文書化された意味(例:grep の 1 は一致なし) |
エラーとコードの対応付けは、os.Exit の呼び出しをあちこちに散らさず、1 か所(main)で行います。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の出力は、主要な UX です。 - ファイル引数では、
-を「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 を含むエンドツーエンドの動作には、Go コマンド自体のテストで使われている形式の testscript(.txtar スクリプト)を使います。
8. バージョン情報
mytool version では debug.ReadBuildInfo() を読み込みます。Go 1.24 以降、メインモジュールのバージョンは VCS タグから取得され、vcs.revision はビルド設定から取得されます。VCS メタデータなしでビルドする場合に限り、-ldflags "-X main.version=..." にフォールバックします。
9. シェル補完
Cobra は completion bash|zsh|fish|powershell を生成します。動的な値には ValidArgsFunction を追加します。Cobra を使わない場合は、静的な補完スクリプトをリリースに含めます。
セクション 3 と 7〜9 の例:references/testing-and-versioning.md。
検証チェックリスト
run(ctx, args, stdin, stdout, stderr)パターン — サブプロセスなしでロジックをテスト可能signal.NotifyContextが接続され、長時間の処理が ctx のキャンセルを尊重する- データは stdout に、診断情報は stderr に出力 — パイプで検証済み
- 終了コード:成功は 0、使い方の誤りは 2、それ以外は文書化されたコードを使用し、
os.Exitは main でのみ使用 - すべてのフラグに使用方法の説明がある —
-hの出力を確認済み - ファイルを指定する箇所では、stdin/stdout に
-を指定できる - 破壊的な操作には明示的なフラグが必要
- argv を介してシークレットを渡さない
- Cobra(使用する場合):すべての箇所で RunE を使用し、SilenceUsage を設定して、context を伝播
- 非 TTY 環境および NO_COLOR の場合は、色表示とスピナーを無効化
-hは 0 で終了し、使い方の誤りは 2 で終了し、Ctrl+C は 130 で終了versionの出力元はビルド情報とし、CLI の表層をゴールデンテストまたは testscript テストでカバー