序章

コマンドラインユーティリティは、追加の構成なしで、箱から出してすぐに役立つことはめったにありません。 適切なデフォルトは重要ですが、便利なユーティリティはユーザーからの構成を受け入れる必要があります。 ほとんどのプラットフォームでは、コマンドラインユーティリティはフラグを受け入れてコマンドの実行をカスタマイズします。 フラグは、コマンド名の後に追加されるキーと値で区切られた文字列です。 Goを使用すると、フラグを使用してフラグを受け入れるコマンドラインユーティリティを作成できます。 flag 標準ライブラリからのパッケージ。

このチュートリアルでは、さまざまな使用方法を探ります。 flag さまざまな種類のコマンドラインユーティリティを構築するためのパッケージ。 フラグを使用してプログラム出力を制御し、フラグと他のデータを混合する位置引数を導入してから、サブコマンドを実装します。

フラグを使用してプログラムの動作を変更する

を使用して flag パッケージには3つのステップが含まれます。最初に変数を定義してフラグ値をキャプチャし、次にGoアプリケーションが使用するフラグを定義し、最後に実行時にアプリケーションに提供されるフラグを解析します。 内のほとんどの機能 flag パッケージは、フラグの定義と、定義した変数へのフラグのバインドに関係しています。 解析フェーズは、 Parse() 関数。

説明のために、標準出力に出力されるメッセージを変更するBooleanフラグを定義するプログラムを作成します。 ある場合 -color フラグが指定されている場合、プログラムはメッセージを青色で印刷します。 フラグが指定されていない場合、メッセージは色なしで印刷されます。

と呼ばれる新しいファイルを作成します boolean.go:

  1. nano boolean.go

次のコードをファイルに追加して、プログラムを作成します。

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 フラグがない場合は変数。

ファイルを保存し、フラグなしでプログラムを実行します。

  1. go run boolean.go

次の出力が表示されます。

Output
Hello, DigitalOcean!

次に、このプログラムを次のコマンドで再度実行します。 -color 国旗:

  1. go run boolean.go -color

出力は同じテキストになりますが、今回は青色になります。

コマンドに渡される値はフラグだけではありません。 ファイル名やその他のデータを送信することもできます。

位置引数の操作

通常、コマンドは、コマンドのフォーカスの対象として機能するいくつかの引数を取ります。 たとえば、 head ファイルの最初の行を出力するコマンドは、多くの場合、次のように呼び出されます。 head example.txt. ファイル example.txt の呼び出しにおける位置引数です head 指図。

The Parse() 関数は、フラグ以外の引数を検出するまで、検出したフラグを解析し続けます。 The flag パッケージはこれらを介して利用可能にします Args()Arg() 機能。

これを説明するために、 head コマンド。指定されたファイルの最初の数行を表示します。

と呼ばれる新しいファイルを作成します head.go 次のコードを追加します。

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 ファイル引数として:

  1. go run head.go -- head.go

The -- セパレータは、によって認識される特別なフラグです。 flag これ以上フラグ引数が続かないことを示すパッケージ。 このコマンドを実行すると、次の出力が表示されます。

Output
package main import ( "bufio" "flag"

使用 -n 出力量を調整するために定義したフラグ:

  1. go run head.go -n 1 head.go

これにより、パッケージステートメントのみが出力されます。

Output
package main

最後に、プログラムは位置引数が指定されていないことを検出すると、次のように標準入力から入力を読み取ります。 head. 次のコマンドを実行してみてください。

  1. echo "fish\nlobsters\nsharks\nminnows" | go run head.go -n 3

次の出力が表示されます。

Output
fish 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 分野。

このプログラムを作成して実行すると、サブコマンドが見やすくなります。 プログラムを作成します。

  1. go build subcommand.go

次に、引数なしでプログラムを実行します。

  1. ./subcommand

次の出力が表示されます。

Output
You must pass a sub-command

次に、プログラムを実行します。 greet サブコマンド:

  1. ./subcommand greet

これにより、次の出力が生成されます。

Output
Hello World !

今使用します -name フラグ greet 名前を指定するには:

  1. ./subcommand greet -name Sammy

プログラムからの次の出力が表示されます。

Output
Hello Sammy !

この例は、Goでより大規模なコマンドラインアプリケーションを構築する方法の背後にあるいくつかの原則を示しています。 FlagSetは、開発者がフラグ解析ロジックによってフラグが処理される場所と方法をより細かく制御できるように設計されています。

結論

フラグを使用すると、プログラムの実行方法をユーザーが制御できるため、より多くのコンテキストでアプリケーションがより便利になります。 ユーザーに便利なデフォルトを提供することは重要ですが、ユーザーの状況では機能しない設定をオーバーライドする機会をユーザーに提供する必要があります。 あなたはそれを見てきました flag パッケージは、ユーザーに構成オプションを提示するための柔軟な選択肢を提供します。 いくつかの単純なフラグを選択するか、サブコマンドの拡張可能なスイートを構築できます。 いずれの場合も、 flag パッケージは、柔軟でスクリプト可能なコマンドラインツールの長い歴史のスタイルでユーティリティを構築するのに役立ちます。

Goプログラミング言語の詳細については、完全なGoシリーズのコーディング方法をご覧ください。