🔰

Go Cobra CLI 作成チュートリアル

に公開

前置き

  • 自分の理解用
  • 公式のユーザーガイドをベースに生成AI(LLM: Gemini-2.5-Pro)に下書きを出力させ、それをベースに自分なりに記述
  • Run関数内の記述はここでは触れない

概要

Go言語で本格的なコマンドラインインターフェース(CLI)ツールを開発する際、多くのプロジェクトで採用されているのが「Cobra」。Cobraは、コマンド、フラグ、引数を柔軟に扱うための豊富な機能を提供し、開発者を力強くサポートする。

この記事では、CobraのインストールからCLIツールを作成するまでの流れを書くものとする。

Cobraとは

Cobraは、Go言語でモダンなCLIアプリケーションを構築するためのライブラリ。一般的に、Cobraを利用するアプリケーションは以下のようなディレクトリ構造になる。

▾ appName/
  ▾ cmd/
      add.go
      your.go
      commands.go
      here.go
  main.go

main.goファイルは非常にシンプルで、その唯一の目的はCobraを初期化すること。

package main

import "{pathToYourApp}/cmd"

func main() {
  cmd.Execute()
}

Cobraを導入し、簡単なCLIツールを作成するまでの流れを見ていく。公式ではcobra-cliというジェネレータツールの利用が最も簡単な方法として推奨されている。

1. 導入とプロジェクトの初期化

cobra-cliツールをインストールした後、プロジェクトを初期化する。これにより、main.goとルートコマンドを定義するcmd/root.goが生成される。

Cobraライブラリのインストール

go getコマンドで最新版のライブラリをインストールする。

go get -u github.com/spf13/cobra@latest

Cobraプロジェクトの初期化

  • initで初期化する。
  • cobra-cliが無いと怒られたら次の「cobra-cliのインストール」に移る。
cobra-cli init {appName}
例:cobra-cli init app

cobra-cliのインストール

Cobraには、アプリケーションの雛形やコマンドファイルを生成するためのcobra-cliというプログラムが用意されている。これを使うのが、Cobraを導入する最も簡単な方法。

go install github.com/spf13/cobra-cli@latest

Windowsを利用している場合、PATHを通す必要がある(勘違いでインストール後すぐ利用できたらすみません)

C:\Users\{ユーザー名}\go\binのディレクトリにcobra-cli.exeがあるので、このパスを環境変数として設定する。設定方法は割愛する。

2. 実行テスト

main.goの実行

main.goをビルドして実行し、結果が返ってくること。

  1. {appName}ディレクトリに移動する
  2. go run ./main.go

CLIツールの説明を変更する

root.goファイルを開くと、以下の内容があることが分かる(Use: "hugo"は別の値かも)

var rootCmd = &cobra.Command{
  Use:   "hugo",
  Short: "Hugo is a very fast static site generator",
  Long: `A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at https://gohugo.io/documentation/`,
  Run: func(cmd *cobra.Command, args []string) {
    // Do Stuff Here
  },
}

これを変更し、以下とする。

var rootCmd = &cobra.Command{
	Use:   "{appName}",
	Short: "チュートリアル",
	Long:  `Cobraを使ったお試しCLIツール`,
}

main.goをビルドして実行し、結果が返ってくること。

go run ./main.go

結果

Cobraを使ったお試しCLIツール

Usage:
  app [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  random      A brief description of your command

Flags:
  -h, --help     help for app
  -t, --toggle   Help message for toggle

Use "app [command] --help" for more information about a command.

実行ファイルの作成

  • 実行ファイルを作成することができる
  • Windowsの場合は拡張子として.exeを{appName}の末尾につける
go build -o {appName}.exe ./main.go
# 例
go build -o app.exe ./main.go

実行ファイル{appName}.exeを起動すると結果が返ってくる

.\app.exe

結果

Cobraを使ったお試しCLIツール

Usage:
  app [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  random      A brief description of your command

Flags:
  -h, --help     help for app
  -t, --toggle   Help message for toggle

Use "app [command] --help" for more information about a command.

3. サブコマンドを作成する

公式のユーザーガイドには以下のように言及されています
(画像は翻訳させたやつです)

イメージとしては、機能をフォルダ分けするような感じ。
何をしたいのか分かりやすくなるし、拡張性が向上するので開発がしやすくなるとのこと。

サブコマンド用のファイルを作成する

ここでは、cmdディレクトリ直下に作成する。root.goファイルを同じ階層。

  1. {appName}ディレクトリに移動する
  2. cobra-cli add randomを実行する

基本構造

cobra.Command構造体を使ってコマンドの振る舞いを設定する。
面倒なのでここではShortとLongの記述はデフォルトとする。

var randomCmd = &cobra.Command{
	Use:   "random",
	Short: "A brief description of your command",
	Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
	Run: func(cmd *cobra.Command, args []string) {
    // ここにコマンド実行時の処理を記述
  },
  • Use: コマンド名
  • Short: ヘルプなどで表示される短い説明文
  • Long: --helpが実行されたときに表示される詳細な説明文
  • Run: コマンドが実行されたときに呼び出される関数で、メインの処理はここに記述する

フラグの定義

コマンドの動作を修飾する。変数を定義し、そこにフラグの値を割り当てるのが一般的な使い方。

細かい使い方はユーザーガイドに記載されてる。

  • ローカルフラグ、永続フラグ
  • 必須フラグ...etc

init()関数内

フラグをinit()関数内で定義する。

func init() {
	rootCmd.AddCommand(randomCmd)

	randomCmd.Flags().IntVarP(&images, "images", "i", 1, "取得する画像の数を指定する")
}

rootCmd.AddCommand(randomCmd)

  • サブコマンド用のファイルをコマンドで作成した際に、自動で追記される。

Flags().IntVarP()

  • int型の値を受け取るフラグを定義する
  • Pがつく関数は短縮形(-i)を同時に設定できる
  • IntVarP()のことをcobra特有の書き方だと思ってユーザーガイドやらGithubリポジトリ上を探して「見つからんな~~~」ってやってたけど、ここにあった・・

(&images, "images", "i", 1, "取得する画像の数を指定する")

  • imagesというグローバル変数に、ユーザーが指定したフラグの値が自動的に格納される
  • 1はユーザーがフラグを指定しなかった際のデフォルトの値
  • go run main.go ramdom -i 5でいうところの-i 5がいわゆるフラグの値

パッケージのドキュメントを元に処理内容をイメージしたメモ

  • func (f *FlagSet) IntVarP(p *int, name, shorthand string, value int, usage string)
    • p *int: フラグの値を格納するint型のポインタ
    • name string: フラグの正式名(Long)。--imagesのように使う
    • shorthand string: フラグの短縮名(Short)。-iのように使う
    • value int: ユーザーがフラグを指定しなかった場合のデフォルト値
    • usage string: --helpを実行したときに表示されるこのフラグの説明文

サブコマンドを指定して実行

go runがすべて成功することを確認する。
※Run関数内の記述や型宣言はここでは触れません。最下部にあるコードを参照。

  1. {appName}ディレクトリに移動する
  2. go run ./main.go random --images 3
  3. go run ./main.go random -i 3
  4. go run ./main.go random

まとめ

  • Cobraを使うことで簡単にCLIツールを作成することができる
  • コマンドやフラグの定義などフレームワークに任せることができる

補足: サブコマンド用のファイルの全文

// app/cmd/random.go

/*
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/spf13/cobra"
)

type Data struct {
	Message string `json:"message"`
	Status  string `json:"status"`
}

var images int

// randomCmd represents the random command
var randomCmd = &cobra.Command{
	Use:   "random",
	Short: "A brief description of your command",
	Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
	Run: func(cmd *cobra.Command, args []string) {

		client := http.Client{
			Timeout: 30 * time.Second,
		}

		for i := range make([]struct{}, images) {
			url := "https://dog.ceo/api/breeds/image/random"

			req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
			if err != nil {
				fmt.Printf("リクエスト%d回目", i)
				fmt.Printf("リクエストの作成に失敗しました: %v", err)
			}

			req.Header.Add("X-My-Client", "Go-Learn")
			res, err := client.Do(req)
			if err != nil {
				fmt.Printf("リクエストの作成に失敗しました: %v", err)
			}

			if res.StatusCode != http.StatusOK {
				fmt.Printf("APIからエラーステータスが返されました: %s", res.Status)
			}

			fmt.Println(res.Header.Get("Content-Type"))

			var data Data
			err = json.NewDecoder(res.Body).Decode(&data)
			if err != nil {
				fmt.Printf("JSONのデコードに失敗しました: %v", err)
			}

			res.Body.Close()
			fmt.Printf("%+v\n", data.Message)

		}
	},
}

func init() {
	rootCmd.AddCommand(randomCmd)

	randomCmd.Flags().IntVarP(&images, "images", "i", 1, "取得する画像の数を指定する")
}

参考

GitHubで編集を提案

Discussion