モジュールのレッスン (4/5)
ミニプロジェクト: フラグ付きの CLI
前のモジュールの構成要素を組み合わせて、flag パッケージを使用したフラグ解析、位置引数の処理、os.Stderr でのエラー、および適切な終了コードなどの小さな慣用的な CLI を構築しましょう。
flag パッケージ
package main
import (
"flag"
"fmt"
"os"
)
func main() {
n := flag.Int("n", 1, "number of repetitions")
upper := flag.Bool("u", false, "print in uppercase")
flag.Parse()
args := flag.Args() // remaining positional arguments
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: echo [-n N] [-u] <text>")
os.Exit(2)
}
text := args[0]
if *upper {
text = strings.ToUpper(text)
}
for i := 0; i < *n; i++ {
fmt.Println(text)
}
}重要なポイント:
flag.Int、flag.String、flag.Bool、flag.Duration(など) は ポインターを返します。*n、*upperを使用します。flag.Parse()は、値を読み取る前、flag.Args()の前に 1 回呼び出す必要があります。flag.Args()は 位置 引数 (--の後または最後のフラグの後の引数) を返します。- 受け入れられるフォーム:
-n 3、-n=3、--n=3。ブール値:-u(true)、-u=false。
従来の終了コード
os.Exit(0) // success
os.Exit(1) // generic error
os.Exit(2) // usage error (incorrect flag usage)シェルとスクリプトはこれらのコードを想定しています。一貫して使用してください。 決して os.Exit を使用してメイン以外の goroutine を終了しないでください。log のフラッシュを含むすべての defer がスキップされます。
標準誤差対標準出力
古くて神聖な Unix ルール:
os.Stdout→ 便利な出力、パイプ可能 (cli | grep ...)。os.Stderr→ 診断、エラー、進行状況バー、使用状況。
fmt.Println("result: 42") // stdout
fmt.Fprintln(os.Stderr, "error: ...") // stderrこのように、cli 2>/dev/null は結果のみを表示し、cli >out.txt は結果のみをリダイレクトし、エラーは表示されたままになります。
log 対 fmt
fmtユーザー出力用。log診断用: タイムスタンプを追加し、デフォルトでos.Stderrに書き込みます。log.Fatal(ログ +os.Exit(1)) およびlog.Panicがあります。
log.SetFlags(log.LstdFlags | log.Lshortfile) // timestamp + file:line
log.Fatal("boom") // prints and exits with 1最新のアプリ (Go 1.21 以降) では、構造化ログ (JSON またはテキスト、レベル、型付きフィールド) に log/slog を使用します。
アーキテクチャ: main をロジックから分離する
func main() {
if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(args []string, stdout, stderr io.Writer) error {
// here use a new flag.FlagSet instead of the global flag.CommandLine
fs := flag.NewFlagSet("echo", flag.ContinueOnError)
fs.SetOutput(stderr)
n := fs.Int("n", 1, "repetitions")
if err := fs.Parse(args); err != nil {
return err
}
// ...
return nil
}利点:
- テスト可能:
bytes.Bufferを stdout/stderr として渡し、出力を検証します。 - グローバル状態なし: 各
FlagSetは分離されています。 mainは小さいままです。IO とデリゲートを準備します。
サブコマンド
サブコマンド (mycli serve、mycli migrate) を含む CLI の場合、外部依存関係のないスキーム:
func main() {
if len(os.Args) < 2 {
usage()
os.Exit(2)
}
switch os.Args[1] {
case "serve":
serveCmd(os.Args[2:])
case "migrate":
migrateCmd(os.Args[2:])
default:
usage()
os.Exit(2)
}
}各サブコマンドには独自の flag.NewFlagSet があります。大規模なプロジェクトの場合、cobra や urfave/cli などのライブラリが、自動生成されたヘルプを使用してこのスキームを構造化します。
演習
デフォルトの 1 のフラグを定義し、「リペティツィオーニ」の説明、フラグの値を解析します。Parse() の値を確認します。
3 回の試行後に解決策が利用可能になります
フラグを設定する必要はありません。解析、スタンパ 'manca argomento' su Stderr e termina con os.Exit(1)。
3 回の試行後に解決策が利用可能になります
CLI でのエラー ストリーム スタンピ メッセージの使用法は Unix フレンドリーですか?
fmt.Fprintln(???, "uso: prog ...")