GoでCLIアプリを開発する！Flagパッケージの活用術


[![Group1551.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/34bdc6ef-5633-4cfa-871a-7a35c12f6b60.png)](https://leapcell.io/?lc_t=q_goflag)


**[Leapcell: The Next-Gen Serverless Platform for Web Hosting](https://leapcell.io/?lc_t=q_goflag)**

### はじめに
`flag` はコマンドラインオプションを解析するために使用されます。Unix 系システムを使用する経験のある人は、コマンドラインオプションに馴染んでいるはずです。たとえば、`ls -al` というコマンドは、現在のディレクトリ内のすべてのファイルとディレクトリの詳細情報を一覧表示します。ここで、`-al` がコマンドラインオプションです。

コマンドラインオプションは、実際の開発でよく使われ、特にツールを書く際には欠かせません。

設定ファイルのパスを指定することができます。たとえば、`postgres -D /usr/local/pgsql/data` は、指定されたデータディレクトリで PostgreSQL サーバーを起動します。
特定のパラメータをカスタマイズすることもできます。たとえば、`python -m SimpleHTTPServer 8080` は、ポート 8080 でリスニングする HTTP サーバーを起動します。指定しない場合は、デフォルトでポート 8000 でリスニングします。

### クイックスタート
ライブラリを学ぶ最初のステップは、もちろんそれを使うことです。まず、`flag` ライブラリの基本的な使い方を見てみましょう：

```go
package main

import (
  "fmt"
  "flag"
)

var (
  intflag int
  boolflag bool
  stringflag string
)

func init() {
  flag.IntVar(&intflag, "intflag", 0, "int flag value")
  flag.BoolVar(&boolflag, "boolflag", false, "bool flag value")
  flag.StringVar(&stringflag, "stringflag", "default", "string flag value")
}

func main() {
  flag.Parse()

  fmt.Println("int flag:", intflag)
  fmt.Println("bool flag:", boolflag)
  fmt.Println("string flag:", stringflag)
}
```

まず、プログラムをコンパイルしてから実行します（私は macOS を使用しています）：

```bash
$ go build -o main main.go
$ ./main -intflag 12 -boolflag 1 -stringflag test
```

出力：
```
int flag: 12
bool flag: true
string flag: test
```

特定のオプションを設定しない場合、対応する変数はデフォルト値を取ります：
```bash
$ ./main -intflag 12 -boolflag 1
```

出力：
```
int flag: 12
bool flag: true
string flag: default
```

設定しなかったオプション `stringflag` がデフォルト値 `default` を持つことがわかります。

また、直接 `go run` を使うこともできます。このコマンドは、まずプログラムをコンパイルして実行可能ファイルを生成し、その後そのファイルを実行し、コマンドライン内の他のオプションをこのプログラムに渡します。
```bash
$ go run main.go -intflag 12 -boolflag 1
```

`-h` を使ってオプションのヘルプ情報を表示することができます：
```bash
$ ./main -h
Usage of /path/to/main:
  -boolflag
        bool flag value
  -intflag int
        int flag value
  -stringflag string
        string flag value (default "default")
```

要約すると、`flag` ライブラリを使う一般的なステップは以下の通りです：
1. オプションの値を格納するためのいくつかのグローバル変数を定義します。たとえば、ここでは `intflag`、`boolflag`、`stringflag` です。
2. `init` メソッド内で `flag.TypeVar` メソッドを使ってオプションを定義します。ここで、`Type` は `Int`、`Uint`、`Float64`、`Bool` などの基本型でもよく、時間間隔の `time.Duration` でもよいです。定義する際には、変数のアドレス、オプション名、デフォルト値、ヘルプ情報を渡します。
3. `main` メソッド内で `flag.Parse` を呼び出して、`os.Args[1:]` からオプションを解析します。`os.Args[0]` は実行可能プログラムのパスなので、除外されます。

### 注意点
`flag.Parse` メソッドは、すべてのオプションが定義された後に呼び出さなければなりません。また、`flag.Parse` が呼び出された後に新しいオプションを定義することはできません。前述のステップに従えば、基本的に問題は発生しません。

`init` はすべてのコードの前に実行されるため、すべてのオプション定義を `init` に入れることで、`main` 関数内で `flag.Parse` が実行されるときにはすべてのオプションがすでに定義されています。

### オプションの形式
`flag` ライブラリは 3 つのコマンドラインオプション形式をサポートしています。
```
-flag
-flag=x
-flag x
```
`-` と `--` のどちらを使っても構いません。それらは同じ機能を持ちます。いくつかのライブラリでは `-` をショートオプション、`--` をロングオプションとして表すこともあります。比較的に、`flag` は使いやすいです。

最初の形式は、ブール型オプションのみをサポートしています。もし現れれば `true` となり、現れなければデフォルト値を取ります。

第 3 の形式は、ブール型オプションをサポートしていません。この形式のブール型オプションは、Unix 系システムで予期せぬ振る舞いをする可能性があるからです。以下のコマンドを考えてみましょう：
```bash
cmd -x *
```
ここで、`*` はシェルのワイルドカードです。`0` や `false` という名前のファイルが存在する場合、ブール型オプション `-x` は値 `false` を取ります。そうでなければ、ブール型オプション `-x` は値 `true` を取ります。そして、このオプションは 1 つの引数を消費します。

ブール型オプションを明示的に `false` に設定したい場合は、`-flag=false` という形式を使うしかありません。

最初の非オプション引数（すなわち `-` または `--` で始まらない引数）または終端記号 `--` に遭遇すると、解析は停止します。以下のプログラムを実行してみましょう：
```bash
$ ./main noflag -intflag 12
```
出力は以下の通りになります：
```
int flag: 0
bool flag: false
string flag: default
```
`noflag` に遭遇したときに解析が停止し、その後のオプション `-intflag` は解析されません。そのため、すべてのオプションはデフォルト値を取ります。

以下のプログラムを実行してみましょう：
```bash
$ ./main -intflag 12 -- -boolflag=true
```
出力は以下の通りになります：
```
int flag: 12
bool flag: false
string flag: default
```
まず、オプション `intflag` が解析され、その値が 12 に設定されます。`--` に遭遇した後、解析は停止し、その後の `--boolflag=true` は解析されないので、`boolflag` オプションはデフォルト値 `false` を取ります。

解析が停止した後、コマンドライン引数がまだ存在する場合、`flag` ライブラリはそれらを保存し、`flag.Args` メソッドを通じてこれらの引数のスライスを取得することができます。

`flag.NArg` メソッドを使って未解析の引数の数を取得することができ、`flag.Arg(i)` を使って位置 `i`（0 から始まる）の引数にアクセスすることができます。

オプションの数も、`flag.NFlag` メソッドを呼び出すことで取得することができます。

上記のプログラムを少し変更します：
```go
func main() {
  flag.Parse()
    
  fmt.Println(flag.Args())
  fmt.Println("Non-Flag Argument Count:", flag.NArg())
  for i := 0; i < flag.NArg(); i++ {
    fmt.Printf("Argument %d: %s\n", i, flag.Arg(i))
  }
  
  fmt.Println("Flag Count:", flag.NFlag())
}
```

このプログラムをコンパイルして実行します：
```bash
$ go build -o main main.go
$ ./main -intflag 12 -- -stringflag test
```

出力：
```
[-stringflag test]
Non-Flag Argument Count: 2
Argument 0: -stringflag
Argument 1: test
```
`--` に遭遇して解析が停止した後、残りの引数 `-stringflag test` は `flag` に保存され、`Args`、`NArg`、`Arg` などのメソッドを通じてアクセスすることができます。

整数型のオプション値は、`1234`（10 進数）、`0664`（8 進数）、`0x1234`（16 進数）のような形式を受け付けることができ、負の値でも構いません。実際、`flag` は内部で `strconv.ParseInt` メソッドを使って文字列を `int` に解析します。

そのため、理論的には `ParseInt` が受け付けるあらゆる形式でも構いません。

ブール型のオプション値は以下のようになります：
- `true` の値：`1`、`t`、`T`、`true`、`TRUE`、`True`；
- `false` の値：`0`、`f`、`F`、`false`、`FALSE`、`False`。

### オプションを定義する別の方法
上では、`flag.TypeVar` を使ってオプションを定義する方法を紹介しました。この方法では、まず変数を定義し、その後変数のアドレスを渡す必要があります。

もう 1 つの方法があります。`flag.Type`（ここで `Type` は `Int`、`Uint`、`Bool`、`Float64`、`String`、`Duration` など）を呼び出すと、自動的に変数を割り当ててくれ、その変数のアドレスを返します。使い方は以前の方法と似ています：
```go
package main

import (
  "fmt"
  "flag"
)

var (
  intflag *int
  boolflag *bool
  stringflag *string
)

func init() {
  intflag = flag.Int("intflag", 0, "int flag value")
  boolflag = flag.Bool("boolflag", false, "bool flag value")
  stringflag = flag.String("stringflag", "default", "string flag value")
}

func main() {
  flag.Parse()
    
  fmt.Println("int flag:", *intflag)
  fmt.Println("bool flag:", *boolflag)
  fmt.Println("string flag:", *stringflag)
}
```

このプログラムをコンパイルして実行します：
```bash
$ go build -o main main.go
$ ./main -intflag 12
```

出力は以下の通りになります：
```
int flag: 12
bool flag: false
string flag: default
```
使う際には参照解除が必要な点を除けば、基本的に以前の方法と同じです。

### 高度な使い方

#### ショートオプションを定義する
`flag` ライブラリは明示的にショートオプションをサポートしていませんが、同じ変数に異なるオプションを設定することで実現することができます。すなわち、2 つのオプションが同じ変数を共有するわけです。

初期化の順序は不確定なので、同じデフォルト値を持つようにする必要があります。そうしないと、このオプションが渡されないときには、振る舞いが不確定になります。

```go
package main

import (
  "fmt"
  "flag"
)

var logLevel string

func init() {
  const (
    defaultLogLevel = "DEBUG"
    usage = "set log level value"
  )
  
  flag.StringVar(&logLevel, "log_type", defaultLogLevel, usage)
  flag.StringVar(&logLevel, "l", defaultLogLevel, usage + "(shorthand)")
}

func main() {
  flag.Parse()

  fmt.Println("log level:", logLevel)
}
```

このプログラムをコンパイルして実行します：
```bash
$ go build -o main main.go
$ ./main -log_type WARNING
$ ./main -l WARNING是继续翻译的内容：
```

ロングオプションとショートオプションのどちらを使っても、以下のように出力されます：
```
log level: WARNING
```

このオプションを渡さない場合、デフォルト値が出力されます：
```bash
$ ./main
log level: DEBUG
```

#### 時間間隔を解析する
基本型をオプションとして使うほか、`flag` ライブラリは `time.Duration` 型、すなわち時間間隔もサポートしています。時間間隔でサポートされる形式は非常に多様で、たとえば `"300ms"`、`"-1.5h"`、`"2h45m"` などです。

時間単位は `ns`、`us`、`ms`、`s`、`m`、`h`、`day` などがあります。実際、`flag` は内部で `time.ParseDuration` を呼び出します。具体的にサポートされる形式は、`time` ライブラリのドキュメントを参照することができます。
```go
package main

import (
  "flag"
  "fmt"
  "time"
)

var (
  period time.Duration
)

func init() {
  flag.DurationVar(&period, "period", 1*time.Second, "sleep period")
}

func main() {
  flag.Parse()
  fmt.Printf("Sleeping for %v...", period)
  time.Sleep(period)
  fmt.Println()
}
```

渡されたコマンドラインオプション `period` に応じて、プログラムは対応する時間だけスリープします。デフォルトは 1 秒です。このプログラムをコンパイルして実行します：
```bash
$ go build -o main main.go
$ ./main
Sleeping for 1s...

$ ./main -period 1m30s
Sleeping for 1m30s...
```

#### オプションをカスタマイズする
`flag` ライブラリが提供するオプション型を使うほか、自分たちでオプション型をカスタマイズすることもできます。標準ライブラリに提供されている例を解析してみましょう：
```go
package main

import (
  "errors"
  "flag"
  "fmt"
  "strings"
  "time"
)

type interval []time.Duration

func (i *interval) String() string {
  return fmt.Sprint(*i)
}

func (i *interval) Set(value string) error {
  if len(*i) > 0 {
    return errors.New("interval flag already set")
  }
  for _, dt := range strings.Split(value, ",") {
    duration, err := time.ParseDuration(dt)
    if err != nil {
      return err
    }
    *i = append(*i, duration)
  }
  return nil
}

var (
  intervalFlag interval
)

func init() {
  flag.Var(&intervalFlag, "deltaT", "comma-seperated list of intervals to use between events")
}

func main() {
  flag.Parse()

  fmt.Println(intervalFlag)
}
```

まず、新しい型を定義します。ここでは、`interval` 型が定義されています。

新しい型は、`flag.Value` インターフェイスを実装しなければなりません：
```go
// src/flag/flag.go
type Value interface {
  String() string
  Set(string) error
}
```

`String` メソッドは、この型の値を形式づけます。`flag.Parse` メソッドが実行され、カスタム型のオプションに遭遇したときには、この型の変数の `Set` メソッドが、オプション値をパラメータとして呼び出されます。

ここでは、`,` で区切られた時間間隔を解析して、スライスに保存しています。

カスタム型のオプションの定義には、`flag.Var` メソッドを使う必要があります。

このプログラムをコンパイルして実行します：
```bash
$ go build -o main main.go
$ ./main -deltaT 30s
[30s]
$ ./main -deltaT 30s,1m,1m30s
[30s 1m0s 1m30s]
```

指定されたオプション値が不正な場合、`Set` メソッドは `error` 型の値を返し、`Parse` の実行は停止し、エラーと使用方法のヘルプが表示されます。
```bash
$ ./main -deltaT 30x
invalid value "30x" for flag -deltaT: time: unknown unit x in duration 30x
Usage of /path/to/main:
  -deltaT value
        comma-seperated list of intervals to use between events
```

#### プログラム内で文字列を解析する
時々、オプションはコマンドラインを通じて渡されません。たとえば、設定テーブルから読み取られたり、プログラムによって生成されたりする場合です。このような場合、`flag.FlagSet` 構造体の関連メソッドを使って、これらのオプションを解析することができます。

実際、私たちが以前に呼び出した `flag` ライブラリのメソッドはすべて、間接的に `FlagSet` 構造体のメソッドを呼び出しています。`flag` ライブラリは、コマンドラインオプションを解析するために、`FlagSet` 型のグローバル変数 `CommandLine` を特別に定義しています。

私たちが以前に呼び出した `flag` ライブラリのメソッドは、単に便利のためのもので、内部的にはすべて `CommandLine` の対応するメソッドを呼び出しています：
```go
// src/flag/flag.go
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)

func Parse() {
  CommandLine.Parse(os.Args[1:])
}

func IntVar(p *int, name string, value int, usage string) {
  CommandLine.Var(newIntValue(value, p), name, usage)
}

func Int(name string, value int, usage string) *int {
  return CommandLine.Int(name, value, usage)
}

func NFlag() int { return len(CommandLine.actual) }

func Arg(i int) string {
  return CommandLine.Arg(i)
}

func NArg() int { return len(CommandLine.args) }
```

同様に、私たちも自分たちの `FlagSet` 型の変数を作成して、オプションを解析することができます。
```go
package main

import (
  "flag"
  "fmt"
)

func main() {
  args := []string{"-intflag", "12", "-stringflag", "test"}

  var intflag int
  var boolflag bool
  var stringflag string

  fs := flag.NewFlagSet("MyFlagSet", flag.ContinueOnError)
  fs.IntVar(&intflag, "intflag", 0, "int flag value")
  fs.BoolVar(&boolflag, "boolflag", false, "bool flag value")
  fs.StringVar(&stringflag, "stringflag", "default", "string flag value")

  fs.Parse(args)
  
  fmt.Println("int flag:", intflag)
  fmt.Println("bool flag:", boolflag)
  fmt.Println("string flag:", stringflag)
}
```

`NewFlagSet` メソッドには 2 つのパラメータがあります。最初のパラメータはプログラム名で、ヘルプを出力するときやエラーが発生したときに表示されます。2 番目のパラメータは、解析中のエラーをどのように処理するかを示すもので、いくつかのオプションがあります：
- `ContinueOnError`：エラーが発生した後も解析を続行します。`CommandLine` はこのオプションを使っています。
- `ExitOnError`：エラーが発生したときに `os.Exit(2)` を呼び出してプログラムを終了します。
- `PanicOnError`：エラーが発生したときに `panic` を起こします。

`flag` ライブラリ内の関連コードをすぐに見てみましょう：

```go
// src/flag/flag.go
func (f *FlagSet) Parse(arguments []string) error {
  f.parsed = true
  f.args = arguments
  for {
    seen, err := f.parseOne()
    if seen {
      continue
    }
    if err == nil {
      break
    }
    switch f.errorHandling {
    case ContinueOnError:
      return err
    case ExitOnError:
      os.Exit(2)
    case PanicOnError:
      panic(err)
    }
  }
  return nil
}
```

これは、直接 `flag` ライブラリのメソッドを使う場合と少し異なります。`FlagSet` が `Parse` メソッドを呼び出すときには、明示的に文字列のスライスをパラメータとして渡す必要があります。なぜなら、`flag.Parse` は内部で `CommandLine.Parse(os.Args[1:])` を呼び出すからです。

# [Leapcell: The Next-Gen Serverless Platform for Web Hosting](https://leapcell.io/?lc_t=q_goflag)

最後に、Go サービスのデプロイに最適なプラットフォームをおすすめします：**[Leapcell](https://leapcell.io/?lc_t=q_goflag)**

[![barndpic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/4ef2a73f-2ea7-4c45-b1d3-4c71de254a34.png)](https://leapcell.io/?lc_t=q_goflag)


### 1. 多言語対応
- JavaScript、Python、Go、または Rust で開発できます。  

### 2. 無制限のプロジェクトを無料でデプロイ
- 使用分のみ課金 — リクエストがなければ、請求されません。

### 3. 抜群のコスト効率
- 使った分だけ課金で、アイドル時の課金はありません。  
- 例：平均応答時間 60ms で 694 万回のリクエストに対応するのに 25 ドルです。  

### 4. 合理化された開発者体験
- 直感的な UI で簡単にセットアップできます。
- 完全自動化された CI/CD パイプラインと GitOps 統合。
- アクション可能なインサイトを得るためのリアルタイム メトリクスとログ。  

### 5. 簡単なスケーラビリティと高性能
- 高い同時実行性を簡単に処理するための自動スケーリング。
- ゼロの運用オーバーヘッド — 構築に集中するだけです。  

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/097e757b-3dd7-4921-bb36-81724f47a95e.png)](https://leapcell.io/?lc_t=q_goflag)


[ドキュメントでさらに詳しく調べる！](https://docs.leapcell.io/)

Leapcell Twitter: [https://x.com/LeapcellHQ](https://x.com/LeapcellHQ)