Goでflagを使ってコマンドライン引数を扱う

多分一億番煎じくらいの内容:tea:
記事としての価値はまあ置いといて:hand:
せっかくやったので整理のために書きます:bulb:

公式ドキュメント
https://golang.org/pkg/flag/

# 非フラグの取得
`Parse()`を呼んだ後に`Args()`で`[]string`として取得できます。

```go:sample1_1.go
package main

import (
    "flag"
    "fmt"
)

func main() {
    flag.Parse()
    args := flag.Args()
    fmt.Println(args)
}
```

```:実行例1-1
$ go run sample1_1.go a b c
[a b c]

$ go run sample1_1.go 1 2 3
[1 2 3]
```

特定の要素だけ取り出したい場合は`Arg(int)`が使えます。こちらも`string`として取り出します。

```go:sample1_2.go
package main

import (
    "flag"
    "fmt"
)

func main() {
    flag.Parse()
    fmt.Println(flag.Arg(0), flag.Arg(1))
}
```

```:実行例1-2-1
$ go run sample1_2.go hoge fuga
hoge fuga
```

範囲外アクセスしたつもりが落ちたりはしませんでした。空文字列が返ってくるっぽいです。

```:実行例1-2-2
$ go run sample1_2.go 1
1
```

# フラグの取得
`型名()`あるいは`型名Var()`でフラグの定義をした後`Parse()`でそれぞれの変数に取得できます。
定義は(フラグ名、デフォルト値、ヘルプメッセージ)の3つからなります。
`型名()`を使った場合、指定した型のポインタが返ってきます。

```go:sample2_1.go
package main

import (
    "flag"
    "fmt"
)

func main() {
    var (
        i = flag.Int("int", 0, "int flag")
        s = flag.String("str", "default", "string flag")
        b = flag.Bool("bool", false, "bool flag")
    )
    flag.Parse()
    fmt.Println(*i, *s, *b)
}
```

```:実行例2-1-1
$ go run sample2_1.go -int 2 -str hello -bool true
2 hello true
```

フラグが指定されない場合はデフォルト値が入ります。

```:実行例2-1-2
$ go run sample2_1.go
0 default false
```

`型名Var()`を使った場合、引数で渡した変数にバインドされます。

```go:sample2_2.go
package main

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

func main() {
    var (
        d time.Duration
        f float64
    )
    flag.DurationVar(&d, "dur", 1 * time.Second, "duration flag")
    flag.Float64Var(&f, "float", 0.1, "float flag")
    flag.Parse()
    fmt.Println(d, f)
}
```

```:実行例2-2-1
$ go run sample2_2.go -dur 1h -float 2.3
1h0m0s 2.3
```

パースできない値を渡すと怒られます。駄目な理由を教えてくれるので親切です。ついでにフラグの種類とデフォルト値も表示してくれます。

```:実行例2-2-2
$ go run sample2_2.go -float str
invalid value "str" for flag -float: strconv.ParseFloat: parsing "str": invalid syntax
Usage of (略):
  -dur duration
    	duration flag (default 1s)
  -float float
    	float flag (default 0.1)
```

定義していないフラグを渡しても怒られます。

```:実行例2-2-3
$ go run sample2_2.go -unkown yeah
flag provided but not defined: -unkown
(以下略)
```

フラグの書き方は`-flag value`でも`-flag=value`でも大丈夫です。ただしboolの場合は`-flag=value`の形式を使ったほうが良いみたいです(下の実行例3参照)。

```:実行例2-2-4
$ go run sample2_2.go -dur=1m -float .5
1m0s 0.5
```

`-h`でヘルプを表示できます。

```:実行例2-2-5
$ go run sample2_2.go -h
Usage of (略):
  -dur duration
    	duration flag (default 1s)
  -float float
    	float flag (default 0.1)
```

# 個数を数える
非フラグは`NArg()`、フラグは`NFlag()`でそれぞれ個数を数えられます。

```go:sample3.go
package main

import (
    "flag"
    "fmt"
)

func main() {
    flag.Int("int", 0, "int flag")
    flag.String("str", "default", "string flag")
    flag.Bool("bool", false, "bool flag")
    flag.Parse()
    fmt.Println(flag.NArg(), flag.NFlag())
}
```

```:実行例3
$ go run sample3.go -int 1 -str foo -bool=true a b
2 3

$ go run sample3.go -int 1 -str foo -bool true a b
3 3

$ go run sample3.go -bool true -int 1 -str foo a b
7 1

$ go run sample3.go a b c -bool=true -str foo
6 0

$ go run sample3.go -bool=true -str foo a b c 
3 2

$ go run sample3.go a b c 
3 0

$ go run sample3.go -bool=true -str foo
0 2
```

フラグがboolかつ後ろに引数が続く場合、`-flag=value`の形式で書かないとフラグの値として認識してくれませんでした。
また、非フラグはフラグより後ろに書かないといけないみたいです。

# おわりに
flag超便利だな！！！って思いました:sparkles:
今までコマンドライン引数って文字列を頑張ってパースしないといけないものだとばかり思っていたもので:sweat_smile:
言語にあらかじめこういったパッケージが用意されているのはホント素晴らしい:tada: