Cobra - Golang learning step 3-2
- 公開日
- カテゴリ:BuildingCLIs
- タグ:Golang,roadmap.sh,学習メモ
roadmap.sh > Go > Building CLIs > Cobra の学習を進めていきます。
※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。
contents
- 開発環境
- 参考 URL
- Cobra
- インストール
- コマンド構造
- 1. 初期化
- 2. root.go
- 1. エントリーポイント作成
- コマンドの実装
- サブコマンドを持つコマンドの実装
- PersistentFlags
- アプリケーションのビルド
開発環境
- チップ: Apple M2 Pro
- OS: macOS Sonoma
- go version: go1.23.2 darwin/arm64
参考 URL
- [official] Cobra Website
- [OpenSource] Cobra Github
- [article] Cobra Package Documentation
- [video] How to write beautiful Golang CLI
Cobra
Cobra は Go のコマンドラインインターフェース (CLI) アプリケーションを作成するためのライブラリです。以下のような特徴があります。
- ネストされたサブコマンドをサポート
- グローバルフラグとローカルフラグの管理
- インテリジェントなサジェスション機能
- 自動的なヘルプの生成
- シェル補完の自動生成(bash, zsh, fish, PowerShell)
インストール
go get -u github.com/spf13/cobra/cobra
コマンド構造
Cobra は以下のようなディレクトリ構造でコマンドを定義します。
myapp/
├── cmd/
│ ├── root.go
│ ├── hello.go
│ ├── thank.go
│ └── wake.go
├── go.mod
└── main.go
プロジェクトルート配下に cmd
ディレクトリを作成し、そこに root.go
とコマンド群を設置、main.go
がエントリーポイントとなります。
cmd/
: コマンドの定義を格納するディレクトリroot.go
: メインとなるコマンドの定義hello.go
: コマンドの定義thank.go
: コマンドの定義wake.go
: コマンドの定義
main.go
: アプリケーションのエントリーポイント
1. 初期化
myapp ディレクトリに移動し、以下のコマンドを実行します。
cd path/to/myapp
go mod init myapp
myapp/
配下に go.mod
が作成されます。
2. root.go
次に、root.go
を作成します。root.go
は CLI アプリケーションの基盤となるファイルです。
- アプリケーションのエントリーポイントとなるルートコマンドの定義
- 共通の設定やフラグの管理
- 設定ファイルの読み込みと環境変数の管理
- エラーハンドリングの基本設定
- ロギングなどの共通機能の初期化
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "挨拶と起こし機能を提供するCLIアプリケーション",
Long: `このアプリケーションは、以下の機能を提供します:
- hello: 指定した言語で挨拶をします(日本語・英語・韓国語・スペイン語)
- thank: 指定した言語で感謝を伝えます(日本語・英語・韓国語・スペイン語)
- wake: 寝ている人を起こします(優しく・強めに)
各コマンドは --help オプションで詳細な使用方法を確認できます。`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
- rootCmd
Use
: コマンドラインで使用する名前Short
: myapp --help で表示される1行の説明Long
: myapp --help で表示される詳細な説明Run
: コマンド実行時に呼び出される関数
- Execute 関数
main.go
から呼び出される関数- コマンドの実行とエラーハンドリングを行う
- エラーが発生した場合は終了コード1でプログラムを終了
- init 関数
- プログラム起動時に自動的に実行される初期化関数
- フラグの設定などを行う
PersistentFlags
: すべてのサブコマンドで使用可能なフラグを設定
1. エントリーポイント作成
次に、エントリーポイントである main.go
を作成します。
package main
import "myapp/cmd"
func main() {
cmd.Execute()
}
ここまでが最小構成です。以下のコマンドを実行してみましょう。ヘルプが表示されます。
cd myapp
go run main.go --help
このアプリケーションは、以下の機能を提供します:
- hello: 指定した言語で挨拶をします(日本語・英語・韓国語・スペイン語)
- thank: 指定した言語で感謝を伝えます(日本語・英語・韓国語・スペイン語)
- wake: 寝ている人を起こします(優しく・強めに)
各コマンドは --help オプションで詳細な使用方法を確認できます。
Usage:
myapp [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
hello 挨拶します
help Help about any command
thank 感謝します
wake 寝ている人を起こします
Flags:
-h, --help help for myapp
コマンドの実装
この CLI アプリケーションの機能を実装します。
hello.go
は挨拶機能を提供するコマンドを実装するファイルです。以下で各部分の詳細を解説します。
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var helloName string // 挨拶する相手の名前を保持
var helloLang string // 挨拶の言語を保持
func init() {
helloCmd := &cobra.Command{
Use: "hello",
Short: "挨拶します",
Run: runHello,
}
helloCmd.Flags().StringVarP(&helloName, "name", "n", "you", "相手の名前")
helloCmd.Flags().StringVarP(&helloLang, "lang", "l", "english", "挨拶の言語")
rootCmd.AddCommand(helloCmd)
}
func runHello(cmd *cobra.Command, args []string) {
var hello string
switch helloLang {
case "japanese":
hello = "こんにちは"
case "korean":
hello = "안녕하세요"
case "spanish":
hello = "Hola"
default:
hello = "Hello"
}
fmt.Printf("%s %s!!\n", hello, helloName)
}
init 関数では、コマンドの定義とフラグの設定を行います。
- コマンドの定義
Use
: コマンド名(myapp helloで使用)Short
: ヘルプでの短い説明Run
: 実際の処理を行う関数を指定
- フラグの設定
StringVarP()
: 文字列型のフラグを定義- 第1引数: 値を格納する変数のポインタ
- 第2引数: フラグの長い名前(--name)
- 第3引数: フラグの短い名前(-n)
- 第4引数: デフォルト値
- 第5引数: ヘルプでの説明
- ルートコマンドへの追加
rootCmd.AddCommand()
: 作成したコマンドをルートコマンドに追加
以下のコマンド、引数で実行できます。
# デフォルト(英語)での挨拶
$ go run main.go hello
Hello you!!
# 名前を指定して挨拶
$ go run main.go hello --name Alice
Hello Alice!!
# 短い形式で名前を指定
$ go run main.go hello -n Bob
Hello Bob!!
# 言語を指定して挨拶
$ go run main.go hello --lang japanese --name 太郎
こんにちは 太郎!!
# 短い形式で言語を指定
$ go run main.go hello -l korean -n 김철수
안녕하세요 김철수!!
同じように、thank.go
も実装します。
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var thankName string
var thankLang string
func init() {
thankCmd := &cobra.Command{
Use: "thank",
Short: "感謝します",
Run: runThank,
}
thankCmd.Flags().StringVarP(&thankName, "name", "n", "you", "相手の名前")
thankCmd.Flags().StringVarP(&thankLang, "lang", "l", "english", "挨拶の言語")
rootCmd.AddCommand(thankCmd)
}
func runThank(cmd *cobra.Command, args []string) {
var thank string
switch thankLang {
case "japanese":
thank = "ありがとう"
case "korean":
thank = "고마워요"
case "spanish":
thank = "Gracias"
default:
thank = "Thank you"
}
fmt.Printf("%s %s!!\n", thank, thankName)
}
サブコマンドを持つコマンドの実装
次は、サブコマンドを持つコマンドの実装例として wake.go
を見ていきましょう。wake コマンドは「優しく起こす」と「強めに起こす」という2つのサブコマンドを持ちます。
package cmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
)
var wakeName string
var wakeTime string
func init() {
wakeCmd := &cobra.Command{
Use: "wake",
Aliases: []string{"w"},
Short: "寝ている人を起こします",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("'gentle' または 'strong' サブコマンドを指定してください。")
fmt.Println("使用例:")
fmt.Println(" wake gentle --name 田中 --time 07:30")
fmt.Println(" wake strong --name 田中")
},
}
gentleCmd := &cobra.Command{
Use: "gentle",
Aliases: []string{"g"},
Short: "優しく起こします",
Run: runWakeGentle,
}
strongCmd := &cobra.Command{
Use: "strong",
Aliases: []string{"s"},
Short: "強めに起こします",
Run: runWakeStrong,
}
gentleCmd.Flags().StringVarP(&wakeName, "name", "n", "you", "起こす人の名前")
gentleCmd.Flags().StringVarP(&wakeTime, "time", "t", "", "現在時刻(デフォルトは現在時刻)")
strongCmd.Flags().StringVarP(&wakeName, "name", "n", "you", "起こす人の名前")
strongCmd.Flags().StringVarP(&wakeTime, "time", "t", "", "現在時刻(デフォルトは現在時刻)")
wakeCmd.AddCommand(gentleCmd)
wakeCmd.AddCommand(strongCmd)
rootCmd.AddCommand(wakeCmd)
}
func getCurrentTime() time.Time {
currentTime := time.Now()
if wakeTime != "" {
parsedTime, err := time.Parse("15:04", wakeTime)
if err == nil {
currentTime = parsedTime
}
}
return currentTime
}
func runWakeGentle(cmd *cobra.Command, args []string) {
currentTime := getCurrentTime()
hour := currentTime.Hour()
var message string
switch {
case hour < 5:
message = fmt.Sprintf("(小声で)%sさん、まだ深夜ですが...必要があって起こしました。申し訳ありません。", wakeName)
case hour < 12:
message = fmt.Sprintf("おはようございます、%sさん。素敵な朝をお迎えください。朝食の用意ができています。", wakeName)
default:
message = fmt.Sprintf("%sさん、お昼を過ぎていますよ。ゆっくり起きましょう。", wakeName)
}
fmt.Println(message)
}
func runWakeStrong(cmd *cobra.Command, args []string) {
currentTime := getCurrentTime()
hour := currentTime.Hour()
var message string
switch {
case hour < 5:
message = fmt.Sprintf("%sさん!!緊急事態です!!直ちに起きてください!!", wakeName)
case hour < 12:
message = fmt.Sprintf("%sさん!もう朝ですよ!!遅刻しますよ!!急いで起きてください!!", wakeName)
default:
message = fmt.Sprintf("%sさん!!こんな時間まで寝てるんですか!?早く起きてください!!", wakeName)
}
fmt.Println(message)
}
まず、フラグの値を格納するグローバル変数を定義します。これまでと同様です。
1. コマンドの初期化
func init() {
wakeCmd := &cobra.Command{
Use: "wake",
Aliases: []string{"w"},
Short: "寝ている人を起こします",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("'gentle' または 'strong' サブコマンドを指定してください。")
fmt.Println("使用例:")
fmt.Println(" wake gentle --name 田中 --time 07:30")
fmt.Println(" wake strong --name 田中")
},
}
wake
コマンドの定義では、以下の新しい要素が登場します。
Aliases
: コマンドの別名を設定できます。この場合 w が別名として設定され、myapp w でも実行可能になります。Run
: サブコマンドが指定されなかった場合に実行される関数です。ここでは使用方法を表示します。
2. サブコマンドの定義
gentle(優しく)と strong(強めに)の2つのサブコマンドを定義します。それぞれに別名(g
と s
)が設定されています。
gentleCmd := &cobra.Command{
Use: "gentle",
Aliases: []string{"g"},
Short: "優しく起こします",
Run: runWakeGentle,
}
strongCmd := &cobra.Command{
Use: "strong",
Aliases: []string{"s"},
Short: "強めに起こします",
Run: runWakeStrong,
}
3. フラグの設定
gentleCmd.Flags().StringVarP(&wakeName, "name", "n", "you", "起こす人の名前")
gentleCmd.Flags().StringVarP(&wakeTime, "time", "t", "", "現在時刻(デフォルトは現在時刻)")
strongCmd.Flags().StringVarP(&wakeName, "name", "n", "you", "起こす人の名前")
strongCmd.Flags().StringVarP(&wakeTime, "time", "t", "", "現在時刻(デフォルトは現在時刻)")
各サブコマンドに同じフラグを設定しています。これにより、両方のサブコマンドで --name
と --time
オプションが使用可能になります。
4. コマンドの階層構造の構築
wakeCmd.AddCommand(gentleCmd)
wakeCmd.AddCommand(strongCmd)
rootCmd.AddCommand(wakeCmd)
wakeCmd.AddCommand()
で wake コマンドに gentle と strong のサブコマンドを追加rootCmd.AddCommand()
で wake コマンド自体をルートコマンドに追加
これにより、以下のような階層構造が作られます。
myapp
└── wake
├── gentle
└── strong
5. 時刻処理のヘルパー関数
func getCurrentTime() time.Time {
currentTime := time.Now()
if wakeTime != "" {
parsedTime, err := time.Parse("15:04", wakeTime)
if err == nil {
currentTime = parsedTime
}
}
return currentTime
}
--time
フラグで時刻が指定されていれば、その時刻を、指定がなければ現在時刻を返す関数です。
6. サブコマンドの実装
gentle と strong の各サブコマンドは、時間帯に応じて異なるメッセージを表示します。
func runWakeGentle(cmd *cobra.Command, args []string) {
currentTime := getCurrentTime()
hour := currentTime.Hour()
var message string
switch {
case hour < 5:
message = fmt.Sprintf("(小声で)%sさん、まだ深夜ですが...必要があって起こしました。申し訳ありません。", wakeName)
case hour < 12:
message = fmt.Sprintf("おはようございます、%sさん。素敵な朝をお迎えください。朝食の用意ができています。", wakeName)
default:
message = fmt.Sprintf("%sさん、お昼を過ぎていますよ。ゆっくり起きましょう。", wakeName)
}
fmt.Println(message)
}
runWakeStrong
も同様の構造ですが、より強めの表現でメッセージを表示します。
使用例
# 優しく起こす(朝7:30の場合)
$ go run main.go wake gentle --name 田中 --time 07:30
おはようございます、田中さん。素敵な朝をお迎えください。朝食の用意ができています。
# 強めに起こす(深夜の場合)
$ go run main.go wake strong --name 田中 --time 03:00
田中さん!!緊急事態です!!直ちに起きてください!!
# エイリアスを使用
$ go run main.go w g -n 田中
おはようございます、田中さん。素敵な朝をお迎えください。朝食の用意ができています。
PersistentFlags
PersistentFlags は、親コマンドで定義されたフラグを全てのサブコマンドで共有・継承できるようにする機能です。通常の Flags は定義されたコマンドでのみ使用できるのに対し、PersistentFlags は全ての子コマンドでも使用できます。
PersistentFlagsは通常、root.go
の init()
関数内で定義します。
func init() {
// 設定ファイルのパスを指定するフラグ
rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "設定ファイルのパス")
}
PersistentFlags と通常の Flags の違い
項目 | PersistentFlags | Flags |
---|---|---|
定義の適用範囲 | コマンド自身およびその配下のサブコマンド | コマンド自身のみ |
用途 | アプリケーション全体やサブコマンド全体で共有する設定 | 特定のコマンドに固有の設定 |
定義の目的 | グローバル設定、環境設定 | 個別機能の制御 |
# 全てのコマンドでconfigフラグが使える
go run main.go --config settings.yaml hello
go run main.go --config settings.yaml thank
go run main.go --config settings.yaml wake
アプリケーションのビルド
今まで go run main.go
で実行していましたが、ビルドを行えば、単独(コンパイル済み)の実行ファイルとして利用できます。
# プロジェクトのルートディレクトリで実行
go build
これにより、以下でコマンドを実行できます。
./myapp hello
Hello you!!
まとめ
- Cobra は Go で CLI アプリケーションを作成するための強力なライブラリ
- ネストされたサブコマンドやフラグ管理を簡単に実現できる
- 自動ヘルプ生成やシェル補完機能を標準で提供
cmd/
ディレクトリでコマンドを構造化する設計が推奨されるroot.go
はルートコマンドや共通設定の基盤となるファイルmain.go
からrootCmd.Execute()
を呼び出してアプリケーションを実行Flags
はコマンドごとのオプションを、PersistentFlags
はサブコマンド全体のオプションを定義- 実行ファイルにビルドすることで、CLI アプリケーションを配布可能
[Next] Step 4-1: GORM
[Prev] Step 3-1: Urfave CLI