Goでコマンドライン引数を受け取る
はじめに
Go で go run main.go
するときの引数を扱いたかったので、その実装の過程の備忘録です。
.env ファイルまたは環境変数の定義がされていればそれも使用し、優先度順ではコマンドライン引数 > .env > 環境変数の順にしたかったのでそのような実装を目指しました。
実装過程
コマンドライン引数を受け取る
Go でコマンドライン引数を取得する方法は os.Args
と flag
パッケージの 2 通りがあるそうです。
簡単かつできることも多そうなので flag
を選択しました!
参考記事:
使い方としては、
-
flag.String()
などに引数名、初期値、説明の順に渡して flag を定義 -
flag.Parse()
で解析 - 変数にはポインタが入ってるので、実体を取り出す
といった感じになります。flag.Args()
で名前がついてない引数も受け取れますし、公式ドキュメントを見るといろんなユースケースに対応してそうです。
func init() {
var name = flag.String("name","John","User name.")
flag.Parse()
fmt.Println(*name) // -> Johnまたは実行時に-name=で渡した引数
}
コマンドは
$ go run main.go -name=Bob
のようにします。ハイフンは 1 つでも 2 つでもよく、boolean でないフラグなら=もなくていいみたいです。
重要なのは flag を定義したあとに flag.Parse()を呼び出すことで、コード内では問題なくても、上の例のようにinit()
関数で flag の解析を行っている場合、go test
したときにテストフラグの処理がテスト用に生成された main 関数で行われるため、それより先に呼び出されるinit()
内の flag.Parse()が呼ばれた時点でテストフラグの解析ができなくなってしまい、テストが通りません。
この問題に関してはinit()
関数の先頭で明示的にtesting.Init()
を呼び出すことで解消できます。
参考記事:
.env ファイルで定義した環境変数を受け取る
godotenvを使い、.envファイルを読み込むことができます。
ただし、通常通りgodotenv.Load()
で読み込むと元から存在する環境変数が優先されるため、今回の場合godotenv.Overload()
を使用する必要があります。
// デフォルトではプロジェクトルートの.envが読まれる。
// 引数にファイル名を渡せば.env.developmentなどの使い分けも可能。
godotenv.Overload()
環境変数を受け取る
ビルトインのos
パッケージを使って環境変数を読み込むことができます。
// 環境変数 HOGE を読み込む
hoge := os.Getenv("HOGE")
また、環境変数のセットや、存在チェックも行うことができます。
// 環境変数 HOGE に "hoge" をセット
os.Setenv("HOGE","hoge")
// 環境変数 HOGE に既に値がセットされていれば hoge に代入され、 ok に true が入る
// セットされていなければhogeには空文字、okにfalseが入る
hoge,ok := os.LookupEnv("HOGE")
コマンドライン引数 > .env > 環境変数の優先順位で読み込む
これらの方法を駆使して、こんな感じで実装してみました!
func init(){
const (
hogeKey = "HOGE"
)
var (
hogeValue = flag.String(hogeKey, "", "hoge.")
)
flag.Parse()
godotenv.Overload()
m := map[string]string{
hogeKey: *hogeValue,
}
for k, v := range m {
if err := overrideEnv(k, v); err != nil {
panic(err)
}
}
}
// 与えられたキーの環境変数を与えられた文字列で上書きします。
// 元の環境変数と上書きする環境変数どちらも存在しない場合、errorを返します。
func overrideEnv(key, value string) error {
if value != "" {
os.Setenv(key, value)
return nil
} else if _, ok := os.LookupEnv(key); !ok {
return fmt.Errorf("%sを指定してください。(-%s=<VALUE>)", key, key)
}
return nil
}
まとめ
flag
に関しては提供されている関数が多く、他のユースケースでも試してみたいなと思いました。
godotenv
に関しても.env.developmentみたいな実践的なパターンでも使ってみたいです!
ではーーー
Discussion