Go言語でコマンドラインツール(CLI)を作る際cobraというライブラリを使うと、ヘルプや設定ファイルの読み込みなど一般的な機能を持ったCLIを簡単に作れるようになっていて便利です。
といった様々なツールがcobraを使って作られています。
cobraにはコマンドラインジェネレーターというのがあり、CLIのボイラープレートを生成することで比較的簡単にCLIを作れるようになっているので、その実装方法を紹介してみたいと思います。
- 前提
- インストール
- CLIのボイラープレートを生成する
- 実行してみる
- サブコマンドを追加
- バイナリを作って実行するには
- cobra initのフラグをファイルで指定
- コマンドライン引数を追加するには
- フラグを追加するには
- 参考資料
前提
以下のバージョンで確認しています。
- go: v1.14.13
- spf13/cobra: v1.1.1
インストール
まず、cobraのジェネレーターをインストールします。
$ go get github.com/spf13/cobra/cobra
このコマンドラインジェネレーターは init
と add
という2つのサブコマンドがあります。
順番に実行してみていきます。
CLIのボイラープレートを生成する
設定ファイル使わないシンプルなCLIを作りたいので、 --viper=false
を指定して cobra init
でボイラープレートを生成します。
$ mkdir -p helloWorldApp && cd helloWorldApp $ cobra init --pkg-name github.com/moritamori/helloWorldApp --viper=false Your Cobra application is ready at /home/takashi/go/src/github.com/helloWorldApp
tree
でファイル構成を見てみると以下のようになっています。
$ tree . ├── LICENSE ├── cmd │ └── root.go └── main.go
main()
関数があるmain.go
、ルートコマンドの実装cmd/root.go
の2つが生成されています。
それぞれのコードを見ていきます。載せるコード量が多くなってしまうので、コメントの部分を除いています。
main.goを開いてみる
main()
関数の中から単純にcmdパッケージの中の Execute()
関数を実行しているだけです。
Excute()
関数はルートコマンドにのみあります。
package main import "github.com/moritamori/helloWorldApp/cmd" func main() { cmd.Execute() }
cmd/root.goを開いてみる
ルートコマンドのファイルです。基本的にジェネレータのボイラープレートのままですが、コード内にあるcobra.Command
の Run
の処理が動くようにコメントアウトを外して、 Hello world!
という文字列の標準出力を追加しています。
実際には、このcmdパッケージの中にコマンドラインの処理を書いていきます。
package cmd import ( "fmt" "github.com/spf13/cobra" "os" ) var rootCmd = &cobra.Command{ Use: "helloWorldApp", Short: "A brief description of your application", Long: `A longer description that spans multiple lines and likely contains examples and usage of using your application. 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) { fmt.Println("hello world!") }, } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } func init() { rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") }
実行してみる
実行するとHello world!が表示されます。
$ go run main.go hello world!
ちなみに、Run
の部分のコメントを外さずに以下のようにして再度実行すると、 Long
に登録しているHelpのメッセージが標準出力に表示されます。
var rootCmd = &cobra.Command{ Use: "helloWorldApp", Short: "A brief description of your application", Long: `A longer description that spans multiple lines and likely contains examples and usage of using your application. 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) { }, }
$ go run main.go
A longer description that spans multiple lines and likely contains
examples and usage of using your application. 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が定義されていない状態だと、ヘルプのメッセージが表示されるという動きになります。
サブコマンドを追加
次にcalkコマンドを追加してみます。
$ cobra add calc
再度、treeを実行しファイル構成を見てみると、新しく cmd/calc.go
が追加されていることが分かります。
$ tree . ├── LICENSE ├── cmd │ ├── calc.go │ └── root.go └── main.go
cmd/calc.goを開いてみる
生成されたファイルを見てみます。
package cmd import ( "fmt" "github.com/spf13/cobra" ) var calcCmd = &cobra.Command{ Use: "calc", 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) { fmt.Println("calc called") }, } func init() { rootCmd.AddCommand(calcCmd) }
ルートコマンドのサブコマンドの違いは
- Excute()関数
- ルートコマンドだけにある
- init()関数
- ルートコマンド
- フラグの設定
- サブコマンド
- ルートコマンドにサブコマンドを追加
- ルートコマンド
という2点です。
では、サブコマンドを実行してみます。
$ go run main.go calc calc called
サブコマンドのRunが実行されたことが分かります。
バイナリを作って実行するには
プロジェクトのディレクトリでgo install
するとソースコードをビルドし、バイナリが $GOPATH/bin
に出来ます。
$ go install
$GOPATH/bin
にパスが通っていることを確認し、バイナリを実行するとgo run
で実行した時と同じ実行結果になることを確認できます。
$ helloWorldApp hello world! $ helloWorldApp calc calc called $ which helloWorldApp /home/takashi/go/bin/helloWorldApp
cobra initのフラグをファイルで指定
上記のサンプルで cobra init
実行時に --viper=false
を指定することで、設定ファイル関連の処理がルートコマンドに入らないようにしました。
などは、毎回フラグを付けるのは煩雑なので .cobra.yaml
ファイルを用意すると便利です。
デフォルトでは、ホームディレクトリ( $HOME/.cobra.yaml
)に配置すると自動的に読み込まれるようになっています。
author: Takashi Morita year: 2021 license: header: This file is part of CLI application foo. text: | {{ .copyright }} This is my license. There are many like it, but this one is mine. My license is my best friend. It is my life. I must master it as I must master my life. useViper: false
Githubでコード管理したい場合など、他のディレクトリに配置したい場合もあるかと思います。
--config
フラグを指定することで、ホームディレクトリ以外の場所に .cobra.yaml
を設置できます。
$ cobra init --pkg-name github.com/moritamori/helloWorldApp2 --config ./.cobra.yaml
コマンドライン引数を追加するには
cobra.Command
に Args
を指定すると引数を渡せるようになります。詳しくは公式のREADMEをご参照ください。
var rootCmd = &cobra.Command{ 〜〜〜 // 最低1つのコマンドライン引数を受け取る Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { fmt.Println(args[0]) }, }
$ go run main.go arg_string arg_string
フラグを追加するには
別の記事にまとめてみたので、よろしければこちらもご参照ください。
simple-minds-think-alike.hatenablog.com