【Golang】cobraで作ったコマンドラインツール(CLI)にフラグを追加する (pflag)

cobra の中のフラグの実装はspf13/pflagという cobra と同じ方が作られている別のライブラリが使われています。このライブラリは標準の flag パッケージと似ているので、使ったことある方は使用感に違和感は感じないかと思います。

以下の記事で作った簡単なコマンドラインツールを元にフラグを追加していきます。 simple-minds-think-alike.hatenablog.com

前提

以下のバージョンで確認しています。

フラグの分類

フラグは以下の2つに分類されます。

  • ローカルフラグ
    • 概要:特定のコマンドだけに反映されます。ルートコマンドに追加したフラグならルートコマンドにのみ、サブコマンドに追加された場合はサブコマンドのみ反映される。
    • コード例: rootCmd.Flags().StringP("name", "n", "", "Your name")
    • コード例の挙動: フラグname がルートコマンドに追加される。
    • cobra内部の動き: Flags()を介してフラグを追加すると lflags にフラグが追加される
  • 永続的フラグ
    • 概要:ルートコマンドとサブコマンドの両方に反映されます。
    • コード例: rootCmd.PersistentFlags().StringP("name", "n", "", "Your name")
    • コード例の挙動: フラグname がルートコマンドとサブコマンドの両方に追加される。
    • cobra内部の動き: PersistentFlags()を介してフラグを追加すると pflags にフラグが追加される

今回は、ローカルフラグを使ってルートコマンドに--nameという文字列型のフラグを追加してみます。

フラグの追加

ルートコマンドcmd/root.goの2箇所に処理を追加します。

  • CLIを初期化する処理(init()内)でフラグを定義
  • CLIを実際の処理 (Run()関数内) でフラグを参照
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.`,
    Run: func(cmd *cobra.Command, args []string) { 
        // ②CLIを実際の処理 (Run()関数内) でフラグを参照
        // nameフラグが渡されていればそれを表示
        name, _ := cmd.Flags().GetString("name")
        if name != "" {
            fmt.Printf("hello world! - %s san!\n", name)
        } else {
            fmt.Println("hello world!")
        }
    },
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

func init() {
    // ①CLIを初期化する処理(init()内)でフラグを定義
    // 第1引数: フラグ名、第2引数: 省略したフラグ名
    // 第3引数: デフォルト値、第4引数: フラグの説明 
    rootCmd.Flags().StringP("name", "n", "", "Your name")
}

実行

追加したフラグを指定する場合と指定しない両方の場合で、コマンドを実行してみます。

# フラグを指定しないで実行した場合
$ go run main.go
hello world!

# フラグを指定した場合
$ go run main.go --name pikotaro
hello world! - pikotaro san!

# 省略した名前でフラグを指定した場合
$ go run main.go -n pikotaro
hello world! - pikotaro san!

nameフラグを指定した場合のみ名前が出力されていて、フラグの有無でプログラムの挙動が変わることを確認できました。

ヘルプを確認

また、ヘルプを表示すると Flags の箇所に追加したフラグに関する記述が追加される事も確認できます。

$ go run main.go -h

Usage:
  helloWorldApp [flags]
  helloWorldApp [command]

Available Commands:
  calc        A brief description of your command
  help        Help about any command

Flags:
  -h, --help          help for helloWorldApp
  -n, --name string   Your name

また、ルートコマンドにローカルフラグを追加したため、 calc サブコマンドにはフラグは追加されていません。

$ go run main.go calc -h

Usage:
  helloWorldApp calc [flags]

Flags:
  -h, --help   help for calc

別の方法でフラグを追加する

Command.StringP() 関数で、文字列のフラグを追加できましたが、他にもフラグを追加する方法があります。

省略したフラグ名なしでフラグを追加

Command.String() 関数を使うと省略したフラグ名なしでフラグを追加できます。

// 第1引数: フラグ名、第2引数: デフォルト値、第3引数: フラグの説明 
cmd.Flags().String("name", "", "Your name")

ヘルプを表示すると以下のように表示され、省略した名前ではフラグを指定できなくなっています。

Flags:
  -h, --help          help for helloWorldApp
      --name string   Your name

変数を指定してフラグを追加

Command.StringVarP()Command.StringVar() 関数を指定すると変数を指定してフラグを追加できます。

変数を指定することで、 cmd.Flags().GetString("name")のようにフラグの値を取得するための処理を書かなくても、値を参照できます。

StringVarPは省略したフラグ名を指定する関数、 StringVarは省略したフラグ名を指定しない関数です。

// 文字列型の変数を定義
var name string

// 第1引数: 変数ポインタ、第2引数: フラグ名、、第3引数: 省略したフラグ名
// 第4引数: デフォルト値、第5引数: フラグの説明 
cmd.Flags().StringVarP(&name, "name", "n", "", "Your name")

// 第1引数: 変数ポインタ、第2引数: フラグ名
// 第3引数: デフォルト値、第4引数: フラグの説明 
cmd.Flags().StringVar(&name, "name", "", "Your name")

// フラグnameの値を標準出力
fmt.Println(name)

図にまとめてみると

それぞれの関数は以下のような位置づけになります。

f:id:moritamorie:20210207182140p:plain

別の種類のフラグを追加する

ここまでが文字列型のフラグの追加方法の共有ですが、同様の方法で他の型のフラグを追加できます。BoolInt のような基本的な型の他にCountIPDuration といった様々な型のフラグを追加可能です。

f:id:moritamorie:20210207182330p:plain

参考資料