猫でもわかるWebプログラミングと副業

本業エンジニアリングマネージャー。副業Webエンジニア。Web開発のヒントや、副業、日常生活のことを書きます。

Golang と Cobra でサクッとコマンドラインツール作成

f:id:yoshiki_utakata:20211130201027p:plain

はじめに

この記事は Go Advent Calendar 1日目の記事です。

qiita.com

go でサクッとコマンドラインが作りたい

最近、よく行う処理を PHP でスクリプト化しています。

例えば、以下のような処理です。

  • CSVを突き合わせて差分を出す
  • 動作確認のために開発環境のAPIを叩く
  • etc...

しかし、PHPの実行環境がないといけない、、PHPのバージョンが違うと動かないなど、不便な点もあります。

そこで、 go で実装して、ビルドしてバイナリさえ用意しておけば、だいたいどんな環境でも動きます。この点が楽だなと思って、このスクリプトを go で書くことにしました。

CLI アプリケーションライブラリ Cobra

コマンドの引数などのパースをするために、 Cobra を導入しました。

github.com

コマンドの構成

今回は例として、2つのコマンドを実装することを考えます。

  • CSVを突き合わせて差分を出す
  • 動作確認のために開発環境のAPIを叩く

最終的な成果物として、2パターンの構成が考えられます。

  • バイナリは1つで、引数で処理を分岐させる
    • 例: バイナリとして utakata というファイルを出力する
    • ./utakata compare-csv : CSVを突き合わせて差分を出す
    • ./utakata test-api : 開発環境のAPIを叩く
  • バイナリを2つ出力する
    • ./compare-csv : CSVを突き合わせて差分を出す
    • ./test-api : 開発環境のAPIを叩く

ビルドや実装が楽なので、引数で処理を分岐させることにしました。

ディレクトリ構成

Go の標準ディレクトリ構成には /cmd というのがあり、今回はこの /cmd 以下にいっぱいファイルを突っ込んでしまったのですが、 本来の /cmd ディレクトリの用途とは違うので、多分別の名前がいいです。

/commands とか /services とかがいいかも。

Go のディレクトリ構成についてはこれを読んでみてください。

https://github.com/golang-standards/project-layout/blob/master/README_ja.md

ディレクトリ構成はこのようになっています

go.mod
go.sum
main.go
cmd/
  compare_csv.go
  test_api.go

Cobra の基本的な使い方

compare_csv.go の例

package cmd

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

// CSVを比較します
func CompareCSV() *cobra.Command {
    // コマンドの定義
    command := &cobra.Command{
        Use: "compare-csv",
        Short: "2つのCSVを比較する",
    }

    // 引数
    command.Flags().StringP(
        "file-a", // 長い引数
        "a", // 短い引数
        "", // デフォルト値
        "比較元ファイル",

    command.Flags().StringP(
        "file-b",
        "b",
        "",
        "比較先ファイル",
    )

    // コマンド本体
    command.RunE = func(cmd *cobra.Command, args []string) error {
        fileA, err := cmd.Flags().GetString("file-a")
        if err != nil {
            return err
        }

        fileB, err := cmd.Flags().GetString("file-b")
        if err != nil {
            return err
        }

        // 以下略
    }
    return command
}

Cobra を使うことで、引数だったりオプションだったりを簡単に設定できます。

utakata compare-csv --file-a hoge.csv --file-b fuga.csv のように使えます。

main.go はこんな感じ

package main

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

func main() {
    rootCmd := &cobra.Command{
        Use: "utakata",
        Short: "便利スクリプト",
        Long: `プレミアム課金で役に立つスクリプト`,
    }

    rootCmd.AddCommand(cmd.CompareCSV())
    rootCmd.AddCommand(cmd.TestAPI())

    err = rootCmd.Execute()
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }
    os.Exit(0)
}

func TestAPI() の方は割愛します。

一応、go.mod はこんな感じ

module utakata

go 1.14

require (
    github.com/joho/godotenv v1.4.0
)

これでビルドします。

go build main.go

すると、 utakata というバイナリが出力されるので、あとは実行するだけです。

./utakata compare-csv ...
./utakata test-api

まとめ

Cobra を使うと、簡単にコマンドラインツールが作れるので、試してみたらどうでしょう。

ある程度ならシェルスクリプトでもできますが、DBからデータ持ってきて加工するとか、goのほうが楽な場合もあります。

おまけ: その他におすすめのライブラリ

godotenv というライブラリが個人的に便利でおすすめです。

github.com

こちらですが、.env ファイルを用意しておき、

DB_HOST=127.0.0.1
DB_USER=test
DB_PASSWORD=password

godotenv.Load() すると、いつもどおり os.Getenv.env の内容が取れるようになるというものです。

package main

import (
    "github.com/joho/godotenv"
)

func main() {
    err := godotenv.Load()
    if err != nil {
        fmt.Println("環境変数が読み込めませんでした。", err)
        os.Exit(1)
    }

    host := os.Getenv("DB_HOST")
}

おすすめで愛用しているので、気になっている人は使ってみてください。