GoのFlagライブラリ:CLI引数の完全なガイド
James Reed
Infrastructure Engineer · Leapcell

はじめに
flag
は、コマンドラインオプションを解析するために使用されます。Unix系のシステムの使用経験がある人は、コマンドラインオプションに馴染みがあるはずです。たとえば、ls -al
コマンドは、現在のディレクトリ内のすべてのファイルとディレクトリに関する詳細情報をリスト表示します。ここで、-al
がコマンドラインオプションです。
コマンドラインオプションは、実際の開発でよく使用されます。特に、ツールを作成する場合に役立ちます。
設定ファイルのパスを指定する場合。たとえば、postgres -D /usr/local/pgsql/data
は、指定されたデータディレクトリで PostgreSQL サーバーを起動します。
特定のパラメータをカスタマイズする場合。たとえば、python -m SimpleHTTPServer 8080
は、ポート 8080 でリッスンする HTTP サーバーを起動します。指定しない場合は、デフォルトでポート 8000 でリッスンします。
クイックスタート
ライブラリを学ぶ最初のステップは、もちろんそれを使用することです。まず、flag
ライブラリの基本的な使用法を見てみましょう。
package main import ( "fmt" "flag" ) var ( intiFlag int boolFlag bool stringFlag string ) func init() { flag.IntVar(&intiFlag, "intiFlag", 0, "intフラグの値 ") flag.BoolVar(&boolFlag, "boolFlag", false, "boolフラグの値 ") flag.StringVar(&stringFlag, "stringFlag", "default", "stringフラグの値 ") } func main() { flag.Parse() fmt.Println("intiFlag:", intiFlag) fmt.Println("boolFlag:", boolFlag) fmt.Println("stringFlag:", stringFlag) }
最初にプログラムをコンパイルしてから実行できます(macOS を使用しています)。
$ go build -o main main.go $ ./main -intiFlag 12 -boolFlag 1 -stringFlag test
出力:
intiFlag: 12
boolFlag: true
stringFlag: test
特定のオプションを設定しない場合、対応する変数はデフォルト値を取ります。
$ ./main -intiFlag 12 -boolFlag 1
出力:
intiFlag: 12
boolFlag: true
stringFlag: default
設定されていないオプション stringFlag
は、デフォルト値 default
を持っています。
go run
を直接使用することもできます。このコマンドは、最初にプログラムをコンパイルして実行可能ファイルを生成し、コマンドラインの他のオプションをこのプログラムに渡してファイルを実行します。
$ go run main.go -intiFlag 12 -boolFlag 1
-h
を使用して、オプションのヘルプ情報を表示できます。
$ ./main -h Usage of /path/to/main: -boolFlag boolフラグの値 -intiFlag int intフラグの値 -stringFlag string stringフラグの値 (default "default")
要約すると、flag
ライブラリを使用する一般的な手順は次のとおりです。
- オプションの値を格納するためのグローバル変数をいくつか定義します。たとえば、ここでは
intiFlag
,boolFlag
,stringFlag
などです。 init
メソッドでflag.TypeVar
メソッドを使用してオプションを定義します。ここで、Type
はInt
,Uint
,Float64
,Bool
のような基本型にすることができます。また、時間間隔time.Duration
にすることもできます。定義するときは、変数のアドレス、オプション名、デフォルト値、およびヘルプ情報を渡します。main
メソッドでflag.Parse
を呼び出して、os.Args[1:]
からオプションを解析します。os.Args[0]
は実行可能プログラムのパスであるため、除外されます。
注意点
flag.Parse
メソッドは、すべてのオプションが定義された後に呼び出す必要があり、flag.Parse
が呼び出された後には新しいオプションを定義できません。前の手順に従っていけば、基本的に問題はありません。
init
はすべてのコードの前に実行されるため、すべてのオプション定義を init
に入れることで、main
関数で flag.Parse
が実行されるときには、すべてのオプションがすでに定義されています。
オプションの形式
flag
ライブラリは、3つのコマンドラインオプション形式をサポートしています。
-フラグ
-フラグ=x
-フラグ x
-
と --
の両方を使用でき、それらは同じ機能を持っています。一部のライブラリは、-
を短いオプションを表し、--
を長いオプションを表すために使用します。比較的、flag
は使いやすいです。
最初の形式は、boolean オプションのみをサポートします。表示される場合は true
で、表示されない場合はデフォルト値を取ります。
3番目の形式は、boolean オプションをサポートしていません。この形式のboolean オプションは、Unix系のシステムで予期しない動作をする可能性があるためです。次のコマンドを検討してください。
cmd -x *
ここで、*
はシェルワイルドカードです。0
または false
という名前のファイルがある場合、boolean オプション -x
は値 false
を取ります。それ以外の場合、boolean オプション -x
は値 true
を取ります。そして、このオプションは1つの引数を消費します。
boolean オプションを明示的に false
に設定する場合は、-flag=false
の形式のみを使用できます。
解析は、最初の非オプション引数(つまり、-
または --
で始まらない引数)またはターミネータ --
が検出されると停止します。次のプログラムを実行します。
$ ./main noflag -intiFlag 12
出力は次のようになります。
intiFlag: 0
boolFlag: false
stringFlag: default
noflag
を検出すると解析が停止し、後続のオプション -intiFlag
が解析されないためです。したがって、すべてのオプションはデフォルト値を取ります。
次のプログラムを実行します。
$ ./main -intiFlag 12 -- -boolFlag=true
出力は次のようになります。
intiFlag: 12
boolFlag: false
stringFlag: default
最初に、オプション intiFlag
が解析され、その値が 12 に設定されます。--
を検出すると、解析が停止し、後続の --boolFlag=true
が解析されないため、boolFlag
オプションはデフォルト値 false
を取ります。
解析が停止した後、まだコマンドライン引数がある場合、flag
ライブラリはそれらを格納し、flag.Args
メソッドを使用してこれらの引数のスライスを取得できます。
flag.NArg
メソッドを使用して解析されていない引数の数を取得し、flag.Arg(i)
を使用して位置 i
(0から始まる)の引数にアクセスします。
オプションの数も、flag.NFlag
メソッドを呼び出すことで取得できます。
上記のプログラムを少し修正します。
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()) }
このプログラムをコンパイルして実行します。
$ go build -o main main.go $ ./main -intiFlag 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
を使用してオプションを定義する方法を紹介しました。このメソッドでは、最初に変数を定義してから、変数のアドレスを渡す必要があります。
別の方法があります。flag.Type
(ここで Type
は Int
, Uint
, Bool
, Float64
, String
, Duration
などにすることができます) を呼び出すと、変数が自動的に割り当てられ、その変数のアドレスが返されます。使い方は前回と似ています。
package main import ( "fmt" "flag" ) var ( intiFlag *int boolFlag *bool stringFlag *string ) func init() { intiFlag = flag.Int("intiFlag", 0, "intフラグの値 ") boolFlag = flag.Bool("boolFlag", false, "boolフラグの値 ") stringFlag = flag.String("stringFlag", "default", "stringフラグの値 ") } func main() { flag.Parse() fmt.Println("intiFlag:", *intiFlag) fmt.Println("boolFlag:", *boolFlag) fmt.Println("stringFlag:", *stringFlag) }
プログラムをコンパイルして実行します。
$ go build -o main main.go $ ./main -intiFlag 12
出力は次のようになります。
intiFlag: 12
boolFlag: false
stringFlag: default
使用時に間接参照が必要なことを除いて、基本的には前の方法と同じです。
高度な使用法
短いオプションの定義
flag
ライブラリは短いオプションを明示的にサポートしていませんが、同じ変数に対して異なるオプションを設定することで実現できます。つまり、2つのオプションが同じ変数を共有します。
初期化の順序は不確実であるため、同じデフォルト値を持つことを確認する必要があります。そうでない場合、このオプションが渡されない場合、動作は不確実になります。
package main import ( "fmt" "flag" ) var logLevel string func init() { const ( defaultLogLevel = "DEBUG" usage = "ログレベル値を設定します" ) flag.StringVar(&logLevel, "log_type", defaultLogLevel, usage) flag.StringVar(&logLevel, "l", defaultLogLevel, usage + "(shorthand)") } func main() { flag.Parse() fmt.Println("ログレベル:", logLevel) }
プログラムをコンパイルして実行します。
$ go build -o main main.go $ ./main -log_type WARNING $ ./main -l WARNING
長いオプションと短いオプションの両方を使用すると、次のようになります。
ログレベル: WARNING
このオプションを渡さない場合は、デフォルト値が出力されます。
$ ./main ログレベル: DEBUG
時間間隔の解析
基本型をオプションとして使用するだけでなく、flag
ライブラリは time.Duration
型、つまり時間間隔もサポートしています。時間間隔にサポートされる形式は、"300ms"
, "-1.5h"
, "2h45m"
など、非常に多様です。
時間単位は、ns
, us
, ms
, s
, m
, h
, day
などにすることができます。実際には、flag
は内部で time.ParseDuration
を呼び出します。特定のサポートされる形式は、time
ライブラリのドキュメントにあります。
package main import ( "flag" "fmt" "time" ) var ( period time.Duration ) func init() { flag.DurationVar(&period, "period", 1*time.Second, "スリープ期間") } func main() { flag.Parse() fmt.Printf("Sleeping for %v...", period) time.Sleep(period) fmt.Println() }
渡されたコマンドラインオプション period
に従って、プログラムは対応する時間スリープします。デフォルトは 1 秒です。プログラムをコンパイルして実行します。
$ go build -o main main.go $ ./main Sleeping for 1s... $ ./main -period 1m30s Sleeping for 1m30s...
オプションのカスタマイズ
flag
ライブラリで提供されるオプション型を使用するだけでなく、オプション型をカスタマイズすることもできます。標準ライブラリで提供されている例を分析してみましょう。
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 フラグはすでに設定されています") } for _, dt := range strings.Split(value, ",") { 期間, err := time.ParseDuration(dt) if err != nil { return err } *i = append(*i, 期間) } return nil } var ( intervalFlag interval ) func init() { flag.Var(&intervalFlag, "deltaT", "イベント間で使用するカンマ区切りの間隔のリスト") } func main() { flag.Parse() fmt.Println(intervalFlag) }
最初に、新しい型を定義します。ここでは、型 interval
が定義されています。
新しい型は、flag.Value
インターフェースを実装する必要があります。
// src/flag/flag.go type Value interface { String() string Set(string) error }
String
メソッドはこの型の値をフォーマットし、flag.Parse
メソッドが実行され、カスタム型のオプションを検出すると、オプション値をパラメータとして持つこの型の変数の Set
メソッドを呼び出します。
ここでは、,
で区切られた時間間隔が解析され、スライスに格納されます。
カスタム型オプションの定義では、flag.Var
メソッドを使用する必要があります。
プログラムをコンパイルして実行します。
$ go build -o main main.go $ ./main -deltaT 30s [30s] $ ./main -deltaT 30s,1m,1m30s [30s 1m0s 1m30s]
指定されたオプション値が無効な場合、Set
メソッドは error
型の値を返し、Parse
の実行が停止し、エラーと使用法のヘルプが出力されます。
$ ./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
の対応するメソッドを呼び出します。
// 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
型の独自の変数を作成して、オプションを解析することもできます。
package main import ( "flag" "fmt" ) func main() { args := []string{"-intiFlag", "12", "-stringFlag", "test"} var intiFlag int var boolFlag bool var stringFlag string fs := flag.NewFlagSet("MyFlagSet", flag.ContinueOnError) fs.IntVar(&intiFlag, "intiFlag", 0, "intフラグの値 ") fs.BoolVar(&boolFlag, "boolFlag", false, "boolフラグの値 ") fs.StringVar(&stringFlag, "stringFlag", "default", "stringフラグの値 ") fs.Parse(args) fmt.Println("intiFlag:", intiFlag) fmt.Println("boolFlag:", boolFlag) fmt.Println("stringFlag:", stringFlag) }
NewFlagSet
メソッドには 2 つのパラメータがあります。最初のパラメータはプログラム名で、ヘルプを出力するときやエラーが発生したときに表示されます。2 番目のパラメータは、解析中にエラーが発生した場合の処理方法であり、いくつかのオプションがあります。
ContinueOnError
: エラーが発生した後も解析を続行します。CommandLine
はこのオプションを使用します。ExitOnError
: エラーが発生したときにos.Exit(2)
を呼び出してプログラムを終了します。PanicOnError
: エラーが発生したときにpanic
を生成します。
flag
ライブラリの関連コードを簡単に見ます。
// 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: Webホスティングのための次世代サーバレスプラットフォーム
最後に、Goサービスをデプロイするのに最適なプラットフォームをおすすめします: Leapcell
1. 多言語のサポート
- JavaScript, Python, Go, または Rust で開発します。
2. 無制限のプロジェクトを無料でデプロイ
- 使用量に応じて支払い - リクエストも料金も不要。
3. 無敵のコスト効率
- アイドル料金なしの従量課金制。
- 例:$25 で、平均応答時間 60ms で 694 万件のリクエストをサポートします。
4. 合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI/CDパイプラインとGitOpsの統合。
- 実行可能なインサイトのためのリアルタイムメトリックとロギング。
5. 楽なスケーラビリティとハイパフォーマンス
- 簡単な高並行処理に対応する自動スケーリング。
- 運用上のオーバーヘッドがゼロ - 構築に集中するだけです。
Leapcell Twitter: https://x.com/LeapcellHQ