Leapcell: The Next-Gen Serverless Platform for Web Hosting
はじめに
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 (
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 を使用しています):
$ go build -o main main.go
$ ./main -intflag 12 -boolflag 1 -stringflag test
出力:
int flag: 12
bool flag: true
string flag: test
特定のオプションを設定しない場合、対応する変数はデフォルト値を取ります:
$ ./main -intflag 12 -boolflag 1
出力:
int flag: 12
bool flag: true
string flag: default
設定しなかったオプション stringflag
がデフォルト値 default
を持つことがわかります。
また、直接 go run
を使うこともできます。このコマンドは、まずプログラムをコンパイルして実行可能ファイルを生成し、その後そのファイルを実行し、コマンドライン内の他のオプションをこのプログラムに渡します。
$ go run main.go -intflag 12 -boolflag 1
-h
を使ってオプションのヘルプ情報を表示することができます:
$ ./main -h
Usage of /path/to/main:
-boolflag
bool flag value
-intflag int
int flag value
-stringflag string
string flag value (default "default")
要約すると、flag
ライブラリを使う一般的なステップは以下の通りです:
- オプションの値を格納するためのいくつかのグローバル変数を定義します。たとえば、ここでは
intflag
、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 つのコマンドラインオプション形式をサポートしています。
-flag
-flag=x
-flag x
-
と --
のどちらを使っても構いません。それらは同じ機能を持ちます。いくつかのライブラリでは -
をショートオプション、--
をロングオプションとして表すこともあります。比較的に、flag
は使いやすいです。
最初の形式は、ブール型オプションのみをサポートしています。もし現れれば true
となり、現れなければデフォルト値を取ります。
第 3 の形式は、ブール型オプションをサポートしていません。この形式のブール型オプションは、Unix 系システムで予期せぬ振る舞いをする可能性があるからです。以下のコマンドを考えてみましょう:
cmd -x *
ここで、*
はシェルのワイルドカードです。0
や false
という名前のファイルが存在する場合、ブール型オプション -x
は値 false
を取ります。そうでなければ、ブール型オプション -x
は値 true
を取ります。そして、このオプションは 1 つの引数を消費します。
ブール型オプションを明示的に false
に設定したい場合は、-flag=false
という形式を使うしかありません。
最初の非オプション引数(すなわち -
または --
で始まらない引数)または終端記号 --
に遭遇すると、解析は停止します。以下のプログラムを実行してみましょう:
$ ./main noflag -intflag 12
出力は以下の通りになります:
int flag: 0
bool flag: false
string flag: default
noflag
に遭遇したときに解析が停止し、その後のオプション -intflag
は解析されません。そのため、すべてのオプションはデフォルト値を取ります。
以下のプログラムを実行してみましょう:
$ ./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
メソッドを呼び出すことで取得することができます。
上記のプログラムを少し変更します:
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 -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
など)を呼び出すと、自動的に変数を割り当ててくれ、その変数のアドレスを返します。使い方は以前の方法と似ています:
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)
}
このプログラムをコンパイルして実行します:
$ go build -o main main.go
$ ./main -intflag 12
出力は以下の通りになります:
int flag: 12
bool flag: false
string flag: default
使う際には参照解除が必要な点を除けば、基本的に以前の方法と同じです。
高度な使い方
ショートオプションを定義する
flag
ライブラリは明示的にショートオプションをサポートしていませんが、同じ変数に異なるオプションを設定することで実現することができます。すなわち、2 つのオプションが同じ変数を共有するわけです。
初期化の順序は不確定なので、同じデフォルト値を持つようにする必要があります。そうしないと、このオプションが渡されないときには、振る舞いが不確定になります。
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)
}
このプログラムをコンパイルして実行します:
$ go build -o main main.go
$ ./main -log_type WARNING
$ ./main -l WARNING是继续翻译的内容:
ロングオプションとショートオプションのどちらを使っても、以下のように出力されます:
log level: WARNING
このオプションを渡さない場合、デフォルト値が出力されます:
$ ./main
log level: 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, "sleep period")
}
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 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
インターフェイスを実装しなければなりません:
// 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{"-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
ライブラリ内の関連コードをすぐに見てみましょう:
// 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
最後に、Go サービスのデプロイに最適なプラットフォームをおすすめします:Leapcell
1. 多言語対応
- JavaScript、Python、Go、または Rust で開発できます。
2. 無制限のプロジェクトを無料でデプロイ
- 使用分のみ課金 — リクエストがなければ、請求されません。
3. 抜群のコスト効率
- 使った分だけ課金で、アイドル時の課金はありません。
- 例:平均応答時間 60ms で 694 万回のリクエストに対応するのに 25 ドルです。
4. 合理化された開発者体験
- 直感的な UI で簡単にセットアップできます。
- 完全自動化された CI/CD パイプラインと GitOps 統合。
- アクション可能なインサイトを得るためのリアルタイム メトリクスとログ。
5. 簡単なスケーラビリティと高性能
- 高い同時実行性を簡単に処理するための自動スケーリング。
- ゼロの運用オーバーヘッド — 構築に集中するだけです。
Leapcell Twitter: https://x.com/LeapcellHQ