Go言語のflag
パッケージを利用して、コマンドラインツールを作成していると、オプションの値をSliceで受け取りたい場合があります。たとえば「同じオプションを複数回指定し、その値をすべてスライスに詰めたい」という場合、どのように実装すればよいでしょうか?
答えだけ先に述べておくと、flag.Value
インターフェースを実装し、パース時にはflag.Var
を呼び出します。
今回は一例を示すにあたって、div
コマンドを作成してみます。div
コマンドは標準入力から数値が与えられると、-d
オプションで与えられた数値で割り切れるかどうかを確認し、割り切れる場合は標準入力の値をそのまま標準出力に出力するというものです。ここでは-d
オプションは複数回指定でき、複数回指定した場合は、1つでも割り切ることができればOKということにします。
動作イメージは次の通りです。ここでは1から15までの数値のうち、3または5で割り切れるものを出力しています。
$ seq 15 | ./div -d 3 -d 5
3
5
6
9
10
12
15
div
コマンドの実装例は次の通りになります。
package main
import (
"bufio"
"flag"
"fmt"
"os"
"strconv"
)
type Divisors []int
func (ds *Divisors) String() string {
return fmt.Sprintf("%v", *ds)
}
func (ds *Divisors) Set(s string) error {
d, err := strconv.Atoi(s)
if err != nil {
return err
}
*ds = append(*ds, d)
return nil
}
func main() {
var divisors Divisors
flag.Var(&divisors, "d", "除数を指定してください")
flag.Parse()
stdin := bufio.NewScanner(os.Stdin)
for stdin.Scan() {
// 標準入力から1行読み込み、整数に変換。
// 変換できない場合は理由を標準エラー出力に出力し、その行はスキップ
line := stdin.Text()
dividend, err := strconv.Atoi(line)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
// 標準入力から読み込んだ整数が、dオプションから取得した整数で割り切れるかどうかを検査。
// 割り切れた場合は標準入力から読み込んだ値を標準出力に出力する。
for _, divisor := range divisors {
if dividend%divisor == 0 {
fmt.Fprintln(os.Stdout, line)
break
}
}
}
}
ここではDivisors
型(実体はint
スライス)を作成、flag.Value
インターフェースの関数String()
とSet()
をそれぞれ実装しています。パース時にflag.Var
関数を呼び出すと、Divisors
型(実体はint
スライス)に-d
オプションの値が格納されることが分かります。