Cobra

GoでモダンなCLIアプリケーションを作成するための強力なライブラリ。gitやkubectlのようなツールにインスパイアされています。

goclicommand-linesubcommands

GitHub概要

spf13/cobra

A Commander for modern Go CLI interactions

ホームページ:https://cobra.dev
スター41,221
ウォッチ369
フォーク2,966
作成日:2013年9月3日
言語:Go
ライセンス:Apache License 2.0

トピックス

clicli-appcobracobra-generatorcobra-librarycommandcommand-cobracommand-linecommandlinegogolanggolang-applicationgolang-libraryposixposix-compliant-flagssubcommands

スター履歴

spf13/cobra Star History
データ取得日時: 2025/7/25 02:05

フレームワーク

Cobra

概要

CobraはGoで強力でモダンなコマンドラインインターフェース(CLI)を作成するためのライブラリです。Kubernetes、Hugo、GitHub CLIなど、多くの有名なプロジェクトで採用されています。サブコマンドベースのCLI、POSIXに準拠したフラグ、自動ヘルプ生成などの機能を提供し、Gitのような複雑なCLIアプリケーションを簡単に構築できます。最新バージョンはv1.8.1(2024年6月更新)で、Go界隈で最も信頼されているCLI構築ライブラリの地位を確立しています。

詳細

CobraはCommandsArgsFlagsの3つの基本概念を中心に構築されています。アプリケーションはAPPNAME COMMAND ARG --FLAGという構造に従い、直感的で一貫性のあるインターフェースを提供します。spf13(Steve Francia)によって開発され、彼はGoogleのGo言語プロダクトリーダーとしても知られています。

Go界隈での圧倒的な地位

CobraがGo CLIライブラリで最も人気な理由:

  • 業界標準の採用: Kubernetes、Hugo、GitHub CLI、Docker CLIなどの主要プロジェクトで使用
  • 完全な機能セット: 単純なフラグ解析から複雑なサブコマンド構造まで対応
  • 優れた開発者体験: 自動補完、ヘルプ生成、エラーメッセージが標準装備
  • 成熟した設計: 長年の開発と実用によって洗練されたAPI
  • フレームワークとライブラリのバランス: 制御権を保ちながら豊富な機能を提供

主な特徴

  • サブコマンドベースのCLI: app serverapp fetchのような階層的なコマンド構造
  • POSIX準拠のフラグ: 短縮形と長形式の両方をサポート(-n--name
  • ネストされたサブコマンド: より組織的で直感的なCLI構造の作成
  • 自動ヘルプ生成: すべてのコマンドとフラグに対して自動的にヘルプを生成
  • インテリジェントな提案: タイポに対して正しいコマンドを提案(Levenshtein距離を使用)
  • シェル自動補完: Bash、Zsh、Fish、PowerShellの補完スクリプトを自動生成
  • コマンドエイリアス: 既存のワークフローを壊さずにコマンド名を変更可能
  • フラググループ: フラグ間の関係を定義(必須共存、相互排他など)

高度な機能

  • カスタム補完: ValidArgsFunctionRegisterFlagCompletionFuncによる動的補完
  • アクティブヘルプ: シェル補完時にユーザーをガイドするヒントメッセージ
  • ライフサイクルフック: PreRunPostRunPersistentPreRunなどのフック関数
  • Viperとの統合: 設定管理ライブラリViperとの簡単な統合(現在は独立)

最新の変更(2024-2025)

  • 依存関係の軽量化: コアライブラリからViperと間接依存関係を除去
  • cobra-cliの分離: コード生成ツールを独立リポジトリ(spf13/cobra-cli)に移行
  • リリース戦略の変更: 季節リリースから汎用ポイントリリースへ移行
  • アクティブヘルプの改善: Tab補完時のインライン警告とヒント機能の強化
  • 継続的な補完機能強化: シェル補完機能の継続的な改善

メリット・デメリット

メリット

  • 大規模で複雑なCLIアプリケーションに最適
  • 豊富な機能とカスタマイズオプション
  • 多くの有名プロジェクトでの採用実績(Kubernetes、Hugo、GitHub CLI等)
  • 優れたドキュメントとコミュニティサポート
  • 自動補完とヘルプ生成による優れたUX
  • プラグイン可能なアーキテクチャ
  • cobra-cliによる自動コード生成とスキャフォールディング
  • 成熟したエコシステムとViperとの統合

デメリット

  • 小規模なプロジェクトには過剰な場合がある(urfave/cliなどの軽量な代替がある)
  • 初期設定がやや複雑(特に初心者には学習コストが高い)
  • pflagライブラリへの依存(標準のflagパッケージとは非互換)
  • 標準フラグライブラリとは異なるAPI
  • 多機能ゆえの複雑性(全機能を理解するのは困難)

主要リンク

書き方の例

基本的な構造

package main

import (
    "fmt"
    "os"
    
    "github.com/spf13/cobra"
)

func main() {
    var rootCmd = &cobra.Command{
        Use:   "myapp",
        Short: "MyAppは素晴らしいCLIアプリケーションです",
        Long: `MyAppは素晴らしいCLIアプリケーションで、
様々な便利な機能を提供します。`,
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("Hello from MyApp!")
        },
    }
    
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

サブコマンドとフラグの例

package main

import (
    "fmt"
    "os"
    
    "github.com/spf13/cobra"
)

var (
    verbose bool
    source  string
)

func main() {
    var rootCmd = &cobra.Command{
        Use:   "app",
        Short: "アプリケーションの簡単な説明",
    }
    
    // グローバルフラグ
    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "詳細な出力")
    
    // serveサブコマンド
    var port int
    var serveCmd = &cobra.Command{
        Use:   "serve",
        Short: "サーバーを起動します",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("サーバーをポート%dで起動しています...\n", port)
            if verbose {
                fmt.Println("詳細モードが有効です")
            }
        },
    }
    serveCmd.Flags().IntVarP(&port, "port", "p", 8080, "サーバーポート")
    
    // configサブコマンド
    var configCmd = &cobra.Command{
        Use:   "config",
        Short: "設定を管理します",
    }
    
    var configSetCmd = &cobra.Command{
        Use:   "set KEY VALUE",
        Short: "設定値を設定します",
        Args:  cobra.ExactArgs(2),
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("設定 %s = %s\n", args[0], args[1])
        },
    }
    
    // コマンドの階層を構築
    rootCmd.AddCommand(serveCmd, configCmd)
    configCmd.AddCommand(configSetCmd)
    
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

動的補完の例

package main

import (
    "fmt"
    "strings"
    
    "github.com/spf13/cobra"
)

func main() {
    var rootCmd = &cobra.Command{
        Use:   "kubectl",
        Short: "Kubernetesクラスタマネージャー",
    }
    
    var getCmd = &cobra.Command{
        Use:   "get TYPE [NAME]",
        Short: "リソースを表示します",
        ValidArgs: []string{"pod", "service", "deployment", "node"},
        Args: cobra.MinimumNArgs(1),
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("%sを取得しています...\n", args[0])
        },
    }
    
    // 動的補完関数
    getCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
        if len(args) == 0 {
            // リソースタイプの補完
            return []cobra.Completion{
                {CompletionText: "pod", Description: "Podリソース"},
                {CompletionText: "service", Description: "Serviceリソース"},
                {CompletionText: "deployment", Description: "Deploymentリソース"},
                {CompletionText: "node", Description: "Nodeリソース"},
            }, cobra.ShellCompDirectiveNoFileComp
        }
        
        if len(args) == 1 {
            // リソース名の補完(実際のクラスタから取得することを想定)
            return getResourceNames(args[0], toComplete), cobra.ShellCompDirectiveNoFileComp
        }
        
        return nil, cobra.ShellCompDirectiveNoFileComp
    }
    
    // フラグの動的補完
    var outputFormat string
    getCmd.Flags().StringVarP(&outputFormat, "output", "o", "json", "出力形式")
    getCmd.RegisterFlagCompletionFunc("output", func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
        return []cobra.Completion{
            {CompletionText: "json", Description: "JSON形式"},
            {CompletionText: "yaml", Description: "YAML形式"},
            {CompletionText: "table", Description: "テーブル形式"},
        }, cobra.ShellCompDirectiveNoFileComp
    })
    
    rootCmd.AddCommand(getCmd)
    rootCmd.Execute()
}

func getResourceNames(resourceType, prefix string) []cobra.Completion {
    // 実際のアプリケーションではAPIを呼び出してリソース名を取得
    mockResources := map[string][]string{
        "pod": {"nginx-pod", "mysql-pod", "redis-pod"},
        "service": {"nginx-svc", "mysql-svc", "redis-svc"},
    }
    
    var completions []cobra.Completion
    if resources, ok := mockResources[resourceType]; ok {
        for _, r := range resources {
            if strings.HasPrefix(r, prefix) {
                completions = append(completions, cobra.Completion{CompletionText: r})
            }
        }
    }
    return completions
}

フラググループの例

package main

import (
    "fmt"
    "github.com/spf13/cobra"
)

func main() {
    var (
        username string
        password string
        token    string
        json     bool
        yaml     bool
        xml      bool
    )
    
    rootCmd := &cobra.Command{
        Use:   "api",
        Short: "APIクライアント",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("APIを呼び出しています...")
        },
    }
    
    // 認証フラグ
    rootCmd.Flags().StringVar(&username, "username", "", "ユーザー名")
    rootCmd.Flags().StringVar(&password, "password", "", "パスワード")
    rootCmd.Flags().StringVar(&token, "token", "", "認証トークン")
    
    // 出力形式フラグ
    rootCmd.Flags().BoolVar(&json, "json", false, "JSON形式で出力")
    rootCmd.Flags().BoolVar(&yaml, "yaml", false, "YAML形式で出力")
    rootCmd.Flags().BoolVar(&xml, "xml", false, "XML形式で出力")
    
    // フラググループの設定
    rootCmd.MarkFlagsRequiredTogether("username", "password") // usernameとpasswordは一緒に必要
    rootCmd.MarkFlagsMutuallyExclusive("json", "yaml", "xml") // 出力形式は1つだけ
    rootCmd.MarkFlagsOneRequired("json", "yaml", "xml") // 出力形式は必須
    
    // 認証方法は1つだけ
    rootCmd.MarkFlagsMutuallyExclusive("token", "username")
    
    rootCmd.Execute()
}

ライフサイクルフックの例

package main

import (
    "fmt"
    "github.com/spf13/cobra"
)

func main() {
    var rootCmd = &cobra.Command{
        Use:   "app",
        Short: "アプリケーション",
        PersistentPreRun: func(cmd *cobra.Command, args []string) {
            fmt.Println("設定を読み込んでいます...")
        },
        PreRun: func(cmd *cobra.Command, args []string) {
            fmt.Println("初期化中...")
        },
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("メイン処理を実行中...")
        },
        PostRun: func(cmd *cobra.Command, args []string) {
            fmt.Println("クリーンアップ中...")
        },
        PersistentPostRun: func(cmd *cobra.Command, args []string) {
            fmt.Println("ログを保存しています...")
        },
    }
    
    rootCmd.Execute()
}

実用的なCLI開発ツールの例

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "strings"
    "time"
    
    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var (
    configFile string
    verbose    bool
    outputFormat string
)

func main() {
    rootCmd := &cobra.Command{
        Use:   "devtool",
        Short: "開発者向けユーティリティツール",
        Long: `devtoolは、日常的な開発タスクを効率化するためのCLIツールです。
プロジェクト管理、ファイル操作、ビルド自動化などの機能を提供します。`,
        PersistentPreRun: func(cmd *cobra.Command, args []string) {
            initConfig()
            if verbose {
                fmt.Printf("設定ファイル: %s\n", viper.ConfigFileUsed())
                fmt.Printf("出力形式: %s\n", outputFormat)
            }
        },
    }

    // グローバルフラグ
    rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "設定ファイル (デフォルト: $HOME/.devtool.yaml)")
    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "詳細な出力")
    rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "table", "出力形式 (table, json, yaml)")

    // サブコマンドを追加
    rootCmd.AddCommand(projectCmd(), buildCmd(), deployCmd())

    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintf(os.Stderr, "エラー: %v\n", err)
        os.Exit(1)
    }
}

func projectCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "project",
        Short: "プロジェクト管理コマンド",
        Long:  "プロジェクトの初期化、テンプレート作成、設定管理を行います。",
    }

    initCmd := &cobra.Command{
        Use:   "init [NAME]",
        Short: "新しいプロジェクトを初期化",
        Args:  cobra.ExactArgs(1),
        Run: func(cmd *cobra.Command, args []string) {
            projectName := args[0]
            template, _ := cmd.Flags().GetString("template")
            force, _ := cmd.Flags().GetBool("force")
            
            if verbose {
                fmt.Printf("プロジェクト初期化: %s (テンプレート: %s)\n", projectName, template)
            }
            
            if !force && directoryExists(projectName) {
                fmt.Fprintf(os.Stderr, "エラー: ディレクトリ '%s' は既に存在します。--force を使用して上書きしてください。\n", projectName)
                os.Exit(1)
            }
            
            createProject(projectName, template)
            fmt.Printf("プロジェクト '%s' が正常に作成されました。\n", projectName)
        },
    }
    
    initCmd.Flags().StringP("template", "t", "basic", "プロジェクトテンプレート (basic, web, api, cli)")
    initCmd.Flags().BoolP("force", "f", false, "既存のディレクトリを上書き")
    
    listCmd := &cobra.Command{
        Use:   "list",
        Short: "利用可能なテンプレートを一覧表示",
        Run: func(cmd *cobra.Command, args []string) {
            templates := []string{"basic", "web", "api", "cli", "microservice"}
            
            fmt.Println("利用可能なテンプレート:")
            for _, template := range templates {
                fmt.Printf("  - %s\n", template)
            }
        },
    }

    cmd.AddCommand(initCmd, listCmd)
    return cmd
}

func buildCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "build",
        Short: "プロジェクトのビルド",
        Long:  "プロジェクトをビルドし、成果物を生成します。",
    }

    var (
        target     string
        optimize   bool
        parallel   int
    )

    buildCmd := &cobra.Command{
        Use:   "run [TARGET...]",
        Short: "指定されたターゲットをビルド",
        Run: func(cmd *cobra.Command, args []string) {
            targets := args
            if len(targets) == 0 {
                targets = []string{"default"}
            }
            
            fmt.Printf("ビルド開始 (並列度: %d, 最適化: %t)\n", parallel, optimize)
            
            for _, target := range targets {
                if verbose {
                    fmt.Printf("  ターゲット '%s' をビルド中...\n", target)
                }
                
                // ビルド処理のシミュレーション
                time.Sleep(time.Millisecond * 500)
                
                switch outputFormat {
                case "json":
                    fmt.Printf(`{"target": "%s", "status": "success", "duration": "500ms"}%s`, target, "\n")
                case "yaml":
                    fmt.Printf("target: %s\nstatus: success\nduration: 500ms\n---\n", target)
                default:
                    fmt.Printf("✓ %s (500ms)\n", target)
                }
            }
            
            fmt.Println("ビルド完了")
        },
    }
    
    buildCmd.Flags().StringVarP(&target, "target", "t", "", "特定のターゲットを指定")
    buildCmd.Flags().BoolVarP(&optimize, "optimize", "O", false, "最適化ビルドを有効化")
    buildCmd.Flags().IntVarP(&parallel, "parallel", "j", 4, "並列ビルド数")
    
    cleanCmd := &cobra.Command{
        Use:   "clean",
        Short: "ビルド成果物をクリーンアップ",
        Run: func(cmd *cobra.Command, args []string) {
            if verbose {
                fmt.Println("ビルド成果物をクリーンアップ中...")
            }
            
            // クリーンアップ処理のシミュレーション
            time.Sleep(time.Millisecond * 200)
            fmt.Println("クリーンアップ完了")
        },
    }

    cmd.AddCommand(buildCmd, cleanCmd)
    return cmd
}

func deployCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "deploy",
        Short: "アプリケーションのデプロイ",
        Long:  "ビルドされたアプリケーションを指定された環境にデプロイします。",
    }

    var (
        environment string
        dryRun      bool
        confirm     bool
    )

    deployCmd := &cobra.Command{
        Use:   "run",
        Short: "デプロイを実行",
        PreRun: func(cmd *cobra.Command, args []string) {
            if environment == "production" && !confirm {
                fmt.Print("本番環境へのデプロイを実行しますか? (y/N): ")
                var response string
                fmt.Scanln(&response)
                if strings.ToLower(response) != "y" && strings.ToLower(response) != "yes" {
                    fmt.Println("デプロイをキャンセルしました")
                    os.Exit(0)
                }
            }
        },
        Run: func(cmd *cobra.Command, args []string) {
            if dryRun {
                fmt.Printf("[ドライラン] %s環境へのデプロイをシミュレーション\n", environment)
            } else {
                fmt.Printf("%s環境へデプロイ中...\n", environment)
            }
            
            // デプロイ処理のシミュレーション
            steps := []string{"設定検証", "アップロード", "サービス再起動", "ヘルスチェック"}
            for i, step := range steps {
                if verbose {
                    fmt.Printf("  [%d/%d] %s...\n", i+1, len(steps), step)
                }
                time.Sleep(time.Millisecond * 300)
            }
            
            if !dryRun {
                fmt.Printf("✓ %s環境へのデプロイが完了しました\n", environment)
            } else {
                fmt.Printf("✓ ドライランが完了しました(実際のデプロイは実行されませんでした)\n")
            }
        },
    }
    
    deployCmd.Flags().StringVarP(&environment, "env", "e", "staging", "デプロイ環境 (staging, production)")
    deployCmd.Flags().BoolVarP(&dryRun, "dry-run", "n", false, "ドライランモード")
    deployCmd.Flags().BoolVarP(&confirm, "yes", "y", false, "確認プロンプトをスキップ")
    
    // 環境の検証
    deployCmd.MarkFlagRequired("env")
    deployCmd.RegisterFlagCompletionFunc("env", func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
        return []cobra.Completion{
            {CompletionText: "staging", Description: "ステージング環境"},
            {CompletionText: "production", Description: "本番環境"},
            {CompletionText: "development", Description: "開発環境"},
        }, cobra.ShellCompDirectiveNoFileComp
    })

    statusCmd := &cobra.Command{
        Use:   "status",
        Short: "デプロイ状況を確認",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("現在のデプロイ状況:\n")
            fmt.Printf("  環境: %s\n", environment)
            fmt.Printf("  最終デプロイ: 2024-01-15 14:30:00\n")
            fmt.Printf("  バージョン: v1.2.3\n")
            fmt.Printf("  ステータス: 正常\n")
        },
    }
    
    statusCmd.Flags().StringVarP(&environment, "env", "e", "staging", "確認する環境")

    cmd.AddCommand(deployCmd, statusCmd)
    return cmd
}

func initConfig() {
    if configFile != "" {
        viper.SetConfigFile(configFile)
    } else {
        home, err := os.UserHomeDir()
        cobra.CheckErr(err)
        
        viper.AddConfigPath(home)
        viper.SetConfigType("yaml")
        viper.SetConfigName(".devtool")
    }
    
    viper.AutomaticEnv()
    
    if err := viper.ReadInConfig(); err == nil && verbose {
        fmt.Printf("設定ファイルを読み込みました: %s\n", viper.ConfigFileUsed())
    }
}

func directoryExists(path string) bool {
    info, err := os.Stat(path)
    if os.IsNotExist(err) {
        return false
    }
    return info.IsDir()
}

func createProject(name, template string) {
    // プロジェクト作成のシミュレーション
    os.MkdirAll(name, 0755)
    
    switch template {
    case "web":
        os.MkdirAll(filepath.Join(name, "static"), 0755)
        os.MkdirAll(filepath.Join(name, "templates"), 0755)
    case "api":
        os.MkdirAll(filepath.Join(name, "handlers"), 0755)
        os.MkdirAll(filepath.Join(name, "models"), 0755)
    case "cli":
        os.MkdirAll(filepath.Join(name, "cmd"), 0755)
    }
    
    // 基本ファイルの作成
    os.WriteFile(filepath.Join(name, "README.md"), []byte("# "+name+"\n"), 0644)
    os.WriteFile(filepath.Join(name, ".gitignore"), []byte("*.log\n*.tmp\n"), 0644)
}

cobra-cliツールの使用例

# cobra-cliのインストール
go install github.com/spf13/cobra-cli@latest

# 新しいCLIアプリケーションの初期化
cobra-cli init myapp

# サブコマンドの追加
cobra-cli add serve
cobra-cli add config
cobra-cli add create -p 'configCmd'

# 生成されたファイル構造
# myapp/
# ├── cmd/
# │   ├── root.go
# │   ├── serve.go
# │   ├── config.go
# │   └── create.go
# ├── main.go
# └── go.mod