Goでフラグパッケージを使用する方法
序章
コマンドラインユーティリティは、追加の構成なしで、箱から出してすぐに役立つことはめったにありません。 適切なデフォルトは重要ですが、便利なユーティリティはユーザーからの構成を受け入れる必要があります。 ほとんどのプラットフォームでは、コマンドラインユーティリティはフラグを受け入れてコマンドの実行をカスタマイズします。 フラグは、コマンド名の後に追加されるキーと値で区切られた文字列です。 Goを使用すると、フラグを使用してフラグを受け入れるコマンドラインユーティリティを作成できます。 flag
標準ライブラリからのパッケージ。
このチュートリアルでは、さまざまな使用方法を探ります。 flag
さまざまな種類のコマンドラインユーティリティを構築するためのパッケージ。 フラグを使用してプログラム出力を制御し、フラグと他のデータを混合する位置引数を導入してから、サブコマンドを実装します。
フラグを使用してプログラムの動作を変更する
を使用して flag
パッケージには3つのステップが含まれます。最初に変数を定義してフラグ値をキャプチャし、次にGoアプリケーションが使用するフラグを定義し、最後に実行時にアプリケーションに提供されるフラグを解析します。 内のほとんどの機能 flag
パッケージは、フラグの定義と、定義した変数へのフラグのバインドに関係しています。 解析フェーズは、 Parse()
関数。
説明のために、標準出力に出力されるメッセージを変更するBooleanフラグを定義するプログラムを作成します。 ある場合 -color
フラグが指定されている場合、プログラムはメッセージを青色で印刷します。 フラグが指定されていない場合、メッセージは色なしで印刷されます。
と呼ばれる新しいファイルを作成します boolean.go
:
- nano boolean.go
次のコードをファイルに追加して、プログラムを作成します。
package main
import (
"flag"
"fmt"
)
type Color string
const (
ColorBlack Color = "\u001b[30m"
ColorRed = "\u001b[31m"
ColorGreen = "\u001b[32m"
ColorYellow = "\u001b[33m"
ColorBlue = "\u001b[34m"
ColorReset = "\u001b[0m"
)
func colorize(color Color, message string) {
fmt.Println(string(color), message, string(ColorReset))
}
func main() {
useColor := flag.Bool("color", false, "display colorized output")
flag.Parse()
if *useColor {
colorize(ColorBlue, "Hello, DigitalOcean!")
return
}
fmt.Println("Hello, DigitalOcean!")
}
この例では、 ANSIエスケープシーケンスを使用して、色付きの出力を表示するように端末に指示します。 これらは特殊な文字シーケンスであるため、新しいタイプを定義することは理にかなっています。 この例では、そのタイプを呼び出しています Color
、およびタイプをとして定義しました string
. 次に、で使用する色のパレットを定義します。 const
次のブロック。 The colorize
後に定義された関数 const
ブロックはこれらの1つを受け入れます Color
定数と string
メッセージを色付けするための変数。 次に、要求された色のエスケープシーケンスを最初に印刷して色を変更するように端末に指示し、次にメッセージを印刷し、最後に特別な色リセットシーケンスを印刷して端末に色のリセットを要求します。
内部 main
、使用します flag.Bool
と呼ばれるブールフラグを定義する関数 color
. この関数の2番目のパラメーターである false
、このフラグが指定されていない場合のデフォルト値を設定します。 あなたが持っているかもしれない期待に反して、これをに設定する true
フラグを指定するとfalseになるような動作は反転しません。 したがって、このパラメータの値はほとんどの場合です false
ブールフラグ付き。
最後のパラメーターは、使用法メッセージとして印刷できる一連のドキュメントです。 この関数から返される値は、 bool
. The flag.Parse
次の行の関数は、このポインタを使用して bool
ユーザーから渡されたフラグに基づく変数。 その後、これの値を確認することができます bool
ポインターを逆参照することによるポインター。 ポインター変数の詳細については、ポインターに関するチュートリアルを参照してください。 このブール値を使用して、次のように呼び出すことができます colorize
いつ -color
フラグが設定され、 fmt.Println
フラグがない場合は変数。
ファイルを保存し、フラグなしでプログラムを実行します。
- go run boolean.go
次の出力が表示されます。
OutputHello, DigitalOcean!
次に、このプログラムを次のコマンドで再度実行します。 -color
国旗:
- go run boolean.go -color
出力は同じテキストになりますが、今回は青色になります。
コマンドに渡される値はフラグだけではありません。 ファイル名やその他のデータを送信することもできます。
位置引数の操作
通常、コマンドは、コマンドのフォーカスの対象として機能するいくつかの引数を取ります。 たとえば、 head
ファイルの最初の行を出力するコマンドは、多くの場合、次のように呼び出されます。 head example.txt
. ファイル example.txt
の呼び出しにおける位置引数です head
指図。
The Parse()
関数は、フラグ以外の引数を検出するまで、検出したフラグを解析し続けます。 The flag
パッケージはこれらを介して利用可能にします Args()
と Arg()
機能。
これを説明するために、 head
コマンド。指定されたファイルの最初の数行を表示します。
と呼ばれる新しいファイルを作成します head.go
次のコードを追加します。
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
)
func main() {
var count int
flag.IntVar(&count, "n", 5, "number of lines to read from the file")
flag.Parse()
var in io.Reader
if filename := flag.Arg(0); filename != "" {
f, err := os.Open(filename)
if err != nil {
fmt.Println("error opening file: err:", err)
os.Exit(1)
}
defer f.Close()
in = f
} else {
in = os.Stdin
}
buf := bufio.NewScanner(in)
for i := 0; i < count; i++ {
if !buf.Scan() {
break
}
fmt.Println(buf.Text())
}
if err := buf.Err(); err != nil {
fmt.Fprintln(os.Stderr, "error reading: err:", err)
}
}
まず、 count
プログラムがファイルから読み取る必要のある行数を保持する変数。 次に、 -n
フラグを使用して flag.IntVar
、オリジナルの動作を反映しています head
プログラム。 この関数を使用すると、独自のポインターを変数に渡すことができます。 flag
を持たない関数 Var
サフィックス。 この違いは別として、残りのパラメータは flag.IntVar
そのフォロー flag.Int
対応するもの:フラグ名、デフォルト値、および説明。 前の例のように、次に呼び出します flag.Parse()
ユーザーの入力を処理します。
次のセクションでは、ファイルを読み取ります。 まず、 io.Reader
ユーザーが要求したファイルに設定される変数、またはプログラムに渡される標準入力。 以内 if
ステートメント、私たちは使用します flag.Arg
すべてのフラグの後の最初の位置引数にアクセスする関数。 ユーザーがファイル名を指定した場合、これが設定されます。 それ以外の場合は、空の文字列になります(""
). ファイル名が存在する場合は、 os.Open
そのファイルを開いて設定する関数 io.Reader
そのファイルに対して前に定義しました。 それ以外の場合は、 os.Stdin
標準入力から読み取ります。
最後のセクションでは、 *bufio.Scanner
で作成 bufio.NewScanner
から行を読み取るには io.Reader
変数 in
. の値まで反復します count
forループを使用して、 break
でラインをスキャンする場合 buf.Scan
を生成します false
値。行数がユーザーの要求数より少ないことを示します。
このプログラムを実行し、を使用して今書いたファイルの内容を表示します head.go
ファイル引数として:
- go run head.go -- head.go
The --
セパレータは、によって認識される特別なフラグです。 flag
これ以上フラグ引数が続かないことを示すパッケージ。 このコマンドを実行すると、次の出力が表示されます。
Outputpackage main
import (
"bufio"
"flag"
使用 -n
出力量を調整するために定義したフラグ:
- go run head.go -n 1 head.go
これにより、パッケージステートメントのみが出力されます。
Outputpackage main
最後に、プログラムは位置引数が指定されていないことを検出すると、次のように標準入力から入力を読み取ります。 head
. 次のコマンドを実行してみてください。
- echo "fish\nlobsters\nsharks\nminnows" | go run head.go -n 3
次の出力が表示されます。
Outputfish
lobsters
sharks
の動作 flag
これまで見てきた関数は、コマンド呼び出し全体を調べることに限定されています。 特にサブコマンドをサポートするコマンドラインツールを作成している場合は、この動作が常に必要になるとは限りません。
FlagSetを使用したサブコマンドの実装
最近のコマンドラインアプリケーションは、多くの場合、「サブコマンド」を実装して、一連のツールを1つのコマンドにバンドルします。 このパターンを使用する最もよく知られているツールは git
. 次のようなコマンドを調べるとき git init
, git
コマンドであり、 init
のサブコマンドです git
. サブコマンドの注目すべき機能の1つは、各サブコマンドが独自のフラグのコレクションを持つことができることです。
Goアプリケーションは、を使用して独自のフラグセットを持つサブコマンドをサポートできます。 flag.(*FlagSet)
タイプ。 これを説明するために、異なるフラグを持つ2つのサブコマンドを使用してコマンドを実装するプログラムを作成します。
と呼ばれる新しいファイルを作成します subcommand.go
次のコンテンツをファイルに追加します。
package main
import (
"errors"
"flag"
"fmt"
"os"
)
func NewGreetCommand() *GreetCommand {
gc := &GreetCommand{
fs: flag.NewFlagSet("greet", flag.ContinueOnError),
}
gc.fs.StringVar(&gc.name, "name", "World", "name of the person to be greeted")
return gc
}
type GreetCommand struct {
fs *flag.FlagSet
name string
}
func (g *GreetCommand) Name() string {
return g.fs.Name()
}
func (g *GreetCommand) Init(args []string) error {
return g.fs.Parse(args)
}
func (g *GreetCommand) Run() error {
fmt.Println("Hello", g.name, "!")
return nil
}
type Runner interface {
Init([]string) error
Run() error
Name() string
}
func root(args []string) error {
if len(args) < 1 {
return errors.New("You must pass a sub-command")
}
cmds := []Runner{
NewGreetCommand(),
}
subcommand := os.Args[1]
for _, cmd := range cmds {
if cmd.Name() == subcommand {
cmd.Init(os.Args[2:])
return cmd.Run()
}
}
return fmt.Errorf("Unknown subcommand: %s", subcommand)
}
func main() {
if err := root(os.Args[1:]); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
このプログラムはいくつかの部分に分かれています: main
関数、 root
関数、およびサブコマンドを実装するための個々の関数。 The main
関数は、コマンドから返されたエラーを処理します。 いずれかの関数がエラーを返す場合、 if
ステートメントはそれをキャッチし、エラーを出力し、プログラムは次のステータスコードで終了します 1
、オペレーティングシステムの残りの部分でエラーが発生したことを示します。 内部 main
、プログラムが呼び出されたすべての引数をに渡します root
. プログラムの名前である最初の引数を削除します(前の例では) ./subcommand
)スライスすることによって os.Args
最初。
The root
関数は定義します []Runner
、すべてのサブコマンドが定義されます。 Runner
は、次のことを可能にするサブコマンドのインターフェイスです。 root
を使用してサブコマンドの名前を取得するには Name()
内容と比較してください subcommand
変数。 を繰り返した後、正しいサブコマンドが見つかったら cmds
変数残りの引数でサブコマンドを初期化し、そのコマンドを呼び出します Run()
方法。
このフレームワークでは他のサブコマンドを簡単に作成できますが、定義するサブコマンドは1つだけです。 The GreetCommand
を使用してインスタンス化されます NewGreetCommand
新しいものを作成する場所 *flag.FlagSet
を使用して flag.NewFlagSet
. flag.NewFlagSet
フラグセットの名前と解析エラーを報告するための戦略の2つの引数を取ります。 The *flag.FlagSet
の名前には、 flag.(*FlagSet).Name
方法。 これを使用します (*GreetCommand).Name()
サブコマンドの名前が私たちが付けた名前と一致するようにメソッド *flag.FlagSet
. NewGreetCommand
また、 -name
前の例と同様の方法でフラグを立てますが、代わりにこれをメソッドとして呼び出します。 *flag.FlagSet
のフィールド *GreetCommand
, gc.fs
. いつ root
を呼び出します Init()
の方法 *GreetCommand
、提供された引数を Parse
の方法 *flag.FlagSet
分野。
このプログラムを作成して実行すると、サブコマンドが見やすくなります。 プログラムを作成します。
- go build subcommand.go
次に、引数なしでプログラムを実行します。
- ./subcommand
次の出力が表示されます。
OutputYou must pass a sub-command
次に、プログラムを実行します。 greet
サブコマンド:
- ./subcommand greet
これにより、次の出力が生成されます。
OutputHello World !
今使用します -name
フラグ greet
名前を指定するには:
- ./subcommand greet -name Sammy
プログラムからの次の出力が表示されます。
OutputHello Sammy !
この例は、Goでより大規模なコマンドラインアプリケーションを構築する方法の背後にあるいくつかの原則を示しています。 FlagSet
は、開発者がフラグ解析ロジックによってフラグが処理される場所と方法をより細かく制御できるように設計されています。
結論
フラグを使用すると、プログラムの実行方法をユーザーが制御できるため、より多くのコンテキストでアプリケーションがより便利になります。 ユーザーに便利なデフォルトを提供することは重要ですが、ユーザーの状況では機能しない設定をオーバーライドする機会をユーザーに提供する必要があります。 あなたはそれを見てきました flag
パッケージは、ユーザーに構成オプションを提示するための柔軟な選択肢を提供します。 いくつかの単純なフラグを選択するか、サブコマンドの拡張可能なスイートを構築できます。 いずれの場合も、 flag
パッケージは、柔軟でスクリプト可能なコマンドラインツールの長い歴史のスタイルでユーティリティを構築するのに役立ちます。
Goプログラミング言語の詳細については、完全なGoシリーズのコーディング方法をご覧ください。