前書き

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

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

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

`+ flag `パッケージを使用するには、3つのステップが必要です。まず、https://www.digitalocean.com/community/tutorials/how-to-use-variables-and-constants-in-go [define variables]でフラグ値をキャプチャします、Goアプリケーションが使用するフラグを定義し、最後に、実行時にアプリケーションに提供されるフラグを解析します。 ` flag `パッケージ内のほとんどの関数は、フラグを定義し、それらを定義した変数にバインドすることに関係しています。 解析フェーズは、 ` Parse()+`関数によって処理されます。

説明のために、標準出力に出力されるメッセージを変更するhttps://www.digitalocean.com/community/tutorials/understanding-boolean-logic-in-go[Boolean]フラグを定義するプログラムを作成します。 「+ -color +」フラグが指定されている場合、プログラムはメッセージを青で印刷します。 フラグが指定されていない場合、メッセージは色なしで印刷されます。

`+ boolean.go +`という新しいファイルを作成します。

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!")
}

この例では、https://en.wikipedia.org/wiki/ANSI_escape_code [ANSI Escape Sequences]を使用して、カラー化された出力を表示するように端末に指示します。 これらは文字の特殊なシーケンスであるため、新しいタイプを定義することは理にかなっています。 この例では、そのタイプを「+ Color 」と呼び、タイプを「 string 」として定義しました。 次に、後続の ` const `ブロックで使用する色のパレットを定義します。 ` const `ブロックの後に定義される ` colorize `関数は、これらの ` Color `定数の1つと、色付けするメッセージの ` string +`変数を受け入れます。 次に、最初に要求された色のエスケープシーケンスを印刷して色を変更するよう端末に指示し、次にメッセージを印刷し、最後に特別な色リセットシーケンスを印刷して端末に色をリセットするように要求します。

+ main +`内で、 `+ flag.Bool`関数を使用して、 + color w`というブールフラグを定義します。 この関数の2番目のパラメーターである `+ false `は、このフラグが指定されていない場合のデフォルト値を設定します。 期待に反して、これを ` true `に設定しても、フラグを指定するとfalseになるような動作は反転しません。 したがって、このパラメーターの値は、ほとんど常にブールフラグを使用した「 false +」です。

最後のパラメーターは、使用法メッセージとして印刷できるドキュメントの文字列です。 この関数から返される値は、 `+ bool `へのポインタです。 次の行の ` flag.Parse `関数はこのポインターを使用して、ユーザーから渡されたフラグに基づいて ` bool `変数を設定します。 ポインターを逆参照することで、この ` bool `ポインターの値を確認できます。 ポインター変数の詳細については、https://www.digitalocean.com/community/conceptual_articles/understanding-pointers-in-go [ポインターに関するチュートリアル]を参照してください。 このブール値を使用して、「-color 」フラグが設定されている場合は「 colorize 」を呼び出し、フラグがない場合は「 fmt.Println +」変数を呼び出します。

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

go run boolean.go

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

OutputHello, DigitalOcean!

次に、 `+ -color +`フラグを指定してこのプログラムを再度実行します。

go run boolean.go -color

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

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

位置引数の使用

通常、コマンドはコマンドのフォーカスの主題として機能する多くの引数を取ります。 たとえば、ファイルの最初の行を出力する「+ head 」コマンドは、しばしば「 head example.txt 」として呼び出されます。 ファイル ` example.txt `は、 ` head +`コマンドの呼び出しにおける位置引数です。

`+ Parse()`関数は、非フラグ引数を検出するまで、検出したフラグを解析し続けます。 ` 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 `変数を定義します。 次に、「 flag.IntVar 」を使用して「 -n 」フラグを定義し、元の「 head 」プログラムの動作をミラーリングします。 この関数を使用すると、独自のhttps://www.digitalocean.com/community/conceptual_articles/understanding-pointers-in-go[pointer]を変数に渡すことができます。 + Var + `サフィックス。 この違いとは別に、 ` flag.IntVar `の残りのパラメーターは、対応する ` flag.Int `のパラメーター(フラグ名、デフォルト値、説明)に従います。 前の例のように、次に ` flag.Parse()+`を呼び出してユーザーの入力を処理します。

次のセクションでファイルを読み取ります。 最初に、ユーザーが要求したファイルに設定されるか、プログラムに渡される標準入力のいずれかに設定される変数「+ io.Reader 」を定義します。 ` if `ステートメント内で、 ` flag.Arg `関数を使用して、すべてのフラグの後の最初の位置引数にアクセスします。 ユーザーがファイル名を指定した場合、これが設定されます。 それ以外の場合は、空の文字列( `” “`)になります。 ファイル名が存在する場合、 ` os.Open `関数を使用してそのファイルを開き、前に定義した ` io.Reader `をそのファイルに設定します。 それ以外の場合、 ` os.Stdin +`を使用して標準入力から読み取ります。

最後のセクションでは、「+ bufio.NewScanner 」で作成された「 * bufio.Scanner 」を使用して、「 io.Reader 」変数「 in 」から行を読み取ります。 https://www.digitalocean.com/community/tutorials/how-to-construct-for-loops-in-go [` for ` loop]を使用して ` count `の値まで反復し、 ` 「 buf.Scan 」で行をスキャンすると「 false 」値が生成され、行数がユーザーが要求した数よりも少ないことを示す場合、 break + `。

このプログラムを実行し、ファイル引数として `+ head.go +`を使用して、作成したファイルの内容を表示します。

go run head.go -- head.go

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

Outputpackage main

import (
       "bufio"
       "flag"

定義した `+ -n +`フラグを使用して、出力の量を調整します。

go run head.go -n 1 head.go

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

Outputpackage main

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

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

出力が表示されます。

Outputfish
lobsters
sharks

これまで見てきた「+ flag +」関数の動作は、コマンド呼び出し全体の検査に限定されていました。 特にサブコマンドをサポートするコマンドラインツールを作成している場合は、常にこの動作が必要なわけではありません。

FlagSetを使用してサブコマンドを実装する

現代のコマンドラインアプリケーションは、多くの場合「サブコマンド」を実装して、単一のコマンドの下に一連のツールをバンドルします。 このパターンを使用する最も有名なツールは `+ 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`関数、およびサブコマンドを実装する個々の関数。 + main +`関数はコマンドから返されたエラーを処理します。 関数がhttps://www.digitalocean.com/community/tutorials/handling-errors-in-go[error]を返す場合、 `+ if +`ステートメントはそれをキャッチし、エラーを出力し、プログラムは終了します。残りのオペレーティングシステムでエラーが発生したことを示す「+1+」のステータスコード。 `+ main +`内で、プログラムが呼び出されたすべての引数を `+ root +`に渡します。 最初に `+ os.Args +`をスライスすることにより、プログラムの名前(前の例では `+。/ subcommand +)である最初の引数を削除します。

+ root +`関数は、すべてのサブコマンドが定義される `+ [] Runner +`を定義します。 `+ Runner +`はhttps://www.digitalocean.com/community/tutorials/how-to-use-interfaces-in-go[interface]で、サブコマンド用に `+ root +`が名前を取得できるようにします+ Name()`を使用してサブコマンドを実行し、コンテンツの ` subcommand `変数と比較します。 ` cmds `変数を反復処理した後に正しいサブコマンドが見つかったら、残りの引数でサブコマンドを初期化し、そのコマンドの ` Run()+`メソッドを呼び出します。

サブフレームを1つだけ定義しますが、このフレームワークでは他のサブコマンドを簡単に作成できます。 + GreetCommand +`は `+ NewGreetCommand +`を使用してインスタンス化され、 `+ flag.NewFlagSet +`を使用して新しい `+ * flag.FlagSet +`を作成します。 `+ flag.NewFlagSet +`は2つの引数を取ります:フラグセットの名前と、解析エラーを報告するための戦略です。 `+ * flag.FlagSet +`の名前は、 `+ flag。(* FlagSet).Name +`メソッドを使用してアクセスできます。 これを `+(* GreetCommand).Name()+`メソッドで使用して、サブコマンドの名前が `+ * flag.FlagSet +`に付けた名前と一致するようにします。 `+ NewGreetCommand +`も前の例と同様の方法で `+ -name +`フラグを定義しますが、代わりにこれを `+ * GreetCommand ++ gcの + * flag.FlagSet + `フィールドのメソッドとして呼び出します.fs + `。 `+ root `が ` * GreetCommand `の ` Init()`メソッドを呼び出すと、提供された引数を ` * flag.FlagSet `フィールドの ` Parse +`メソッドに渡します。

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

go build subcommand.go

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

./subcommand

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

OutputYou must pass a sub-command

次に、 `+ greet +`サブコマンドを使用してプログラムを実行します。

./subcommand greet

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

OutputHello World !

ここで、 `+ -name `フラグと ` greet +`を使用して名前を指定します。

./subcommand greet -name

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

OutputHello  !

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

結論

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

Goプログラミング言語の詳細については、https://www.digitalocean.com/community/tutorial_series/how-to-code-in-go [Goのコーディング方法]シリーズをご覧ください。