Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 1750] ファイルの概要

このコミットは、Go言語の標準ライブラリであるflagパッケージに対する変更です。具体的には、src/lib/Makefilesrc/lib/flag.goの2つのファイルが修正されています。flag.goは、コマンドライン引数を解析するための機能を提供するGoのソースファイルであり、Makefileはそのビルドプロセスを定義するファイルです。

コミット

commit 0ea27e345e006b177d8ffc7e86188accf871d0d0
Author: Rob Pike <r@golang.org>
Date:   Wed Mar 4 22:43:51 2009 -0800

    flag: document
    also write to stderr not stdout

    R=rsc
    DELTA=48  (38 added, 2 deleted, 8 changed)
    OCL=25729
    CL=25733

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/0ea27e345e006b177d8ffc7e86188accf871d0d0

元コミット内容

このコミットの目的は、flagパッケージのドキュメントを整備することと、フラグのデフォルト値や使用方法に関するメッセージの出力先を標準出力(stdout)から標準エラー出力(stderr)に変更することです。

変更の背景

Go言語の初期段階において、標準ライブラリは活発に開発・改善されていました。flagパッケージは、コマンドライン引数を扱う上で非常に基本的な機能を提供するため、その使いやすさと堅牢性は重要です。このコミットが行われた2009年3月は、Go言語が一般に公開される前の時期であり、ライブラリのAPI設計やドキュメントの整備が精力的に進められていたと考えられます。

この変更の背景には、以下の2つの主要な理由が考えられます。

  1. ドキュメントの不足: flagパッケージの関数や型には、その時点では十分なドキュメントコメントがありませんでした。これにより、開発者がパッケージの機能を理解し、適切に使用することが困難でした。ドキュメントを追加することで、APIの意図が明確になり、利用者がより簡単にflagパッケージを使いこなせるようになります。
  2. 標準エラー出力への変更: コマンドラインツールの慣習として、プログラムの通常の出力は標準出力(stdout)へ、エラーメッセージや診断情報、ヘルプメッセージなどは標準エラー出力(stderr)へ出力するのが一般的です。これは、スクリプトなどでプログラムの出力をパイプで別のコマンドに渡す際に、ヘルプメッセージなどが混入して処理を妨げないようにするためです。このコミット以前は、flagパッケージのヘルプメッセージやデフォルト値の表示が標準出力に行われていたため、この慣習に合わせる形で標準エラー出力への変更が実施されました。

前提知識の解説

Go言語のflagパッケージ

flagパッケージは、Goプログラムがコマンドライン引数を解析するための機能を提供します。これにより、ユーザーはプログラムの実行時にオプション(フラグ)を指定して、その挙動を制御できます。

  • フラグの定義: flag.StringVar, flag.IntVar, flag.BoolVarなどの関数を使って、文字列、整数、真偽値などの型のフラグを定義します。これらの関数は、フラグの名前、デフォルト値、そしてヘルプメッセージを受け取ります。
  • フラグの解析: flag.Parse()関数を呼び出すことで、定義されたフラグとコマンドライン引数を照合し、フラグに指定された値を対応する変数に設定します。
  • ヘルプメッセージ: flagパッケージは、定義されたフラグに基づいて自動的にヘルプメッセージを生成する機能を持っています。通常、-h--helpといったフラグが指定された際に表示されます。

標準入出力(Standard I/O)

Unix系OSにおける標準入出力は、プロセスが外部と通信するための基本的な仕組みです。

  • 標準入力 (stdin): プロセスがデータを読み込むための入力ストリーム。通常はキーボードに接続されていますが、パイプやリダイレクトによってファイルや他のコマンドの出力に接続することもできます。
  • 標準出力 (stdout): プロセスが通常の出力データを書き込むための出力ストリーム。通常はターミナルに表示されますが、パイプやリダイレクトによってファイルや他のコマンドの入力に接続することもできます。
  • 標準エラー出力 (stderr): プロセスがエラーメッセージや診断情報を書き込むための出力ストリーム。通常はターミナルに表示されますが、標準出力とは独立しているため、エラーメッセージだけをファイルにリダイレクトするといったことが可能です。

コマンドラインツールの設計において、通常のデータ出力とエラー/診断情報を分離することは非常に重要です。これにより、スクリプトでの自動処理が容易になり、エラーハンドリングが明確になります。

Go言語のfmtパッケージとosパッケージ

  • fmtパッケージ: フォーマットされた入出力を扱うためのパッケージです。fmt.Printfは標準出力にフォーマットされた文字列を出力し、fmt.Fprintfは指定されたio.Writerにフォーマットされた文字列を出力します。
  • osパッケージ: オペレーティングシステムとのインタフェースを提供するパッケージです。os.Stderrは標準エラー出力に接続されたio.Writerインターフェースを持つファイル記述子です。

技術的詳細

このコミットは、主に以下の2つの技術的な変更を含んでいます。

  1. ドキュメントコメントの追加: src/lib/flag.goの多くの関数、型、メソッドに対して、Goのドキュメントコメント(//で始まるコメント)が追加されました。これらのコメントは、godocツールによって自動的にドキュメントを生成するために使用されます。例えば、FlagValueインターフェース、Flag構造体、VisitAllVisitLookupSetPrintDefaultsUsageArgNArg、そして各種フラグ定義関数(BoolVar, Bool, IntVar, Intなど)に詳細な説明が追加されています。これにより、flagパッケージのAPIがより自己記述的になり、開発者がその機能と使い方を容易に理解できるようになりました。

  2. 出力ストリームの変更: flag.go内のPrintDefaults()関数とUsage()関数において、メッセージの出力先が変更されました。

    • 変更前: fmt.Printfや組み込みのprint関数(Goの初期バージョンでは存在した)を使用して標準出力にメッセージを出力していました。
    • 変更後: fmt.Fprintf(os.Stderr, ...)を使用するように変更されました。これにより、フラグのデフォルト値や使用方法に関するメッセージが標準エラー出力にリダイレクトされるようになりました。この変更は、コマンドラインツールの標準的な慣習に準拠するためのものです。
  3. 依存関係の追加: src/lib/Makefileにおいて、flag.6のビルド依存関係にos.dirinstallstrconv.dirinstallが追加されました。

    • os.dirinstall: flag.go内でosパッケージがインポートされるようになったため、その依存関係を明示しています。os.Stderrを使用するためにosパッケージが必要になります。
    • strconv.dirinstall: flag.goの変更差分には直接的なstrconvパッケージのインポートや使用箇所の追加は見られませんが、既存のコードでstrconvパッケージが使用されている可能性があり、その依存関係が明示されたものと考えられます。flagパッケージは文字列と数値の変換を頻繁に行うため、strconvへの依存は自然です。

これらの変更は、flagパッケージの使いやすさ、ドキュメントの品質、そしてコマンドラインツールの標準的な振る舞いへの準拠を大幅に向上させるものです。

コアとなるコードの変更箇所

src/lib/Makefile

--- a/src/lib/Makefile
+++ b/src/lib/Makefile
@@ -96,7 +96,7 @@ test: test.files
 bignum.6: fmt.dirinstall
 bufio.6: io.dirinstall os.dirinstall
 exec.6: os.dirinstall
-flag.6: fmt.dirinstall
+flag.6: fmt.dirinstall os.dirinstall strconv.dirinstall
 log.6: fmt.dirinstall io.dirinstall os.dirinstall time.dirinstall
 once.6: sync.dirinstall
 strings.6: utf8.install

src/lib/flag.go

--- a/src/lib/flag.go
+++ b/src/lib/flag.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package flag
-
 /*
  * Flags
  *\
@@ -41,9 +39,11 @@ package flag
  *\tInteger flags accept 1234, 0664, 0x1234 and may be negative.\n  *\tBoolean flags may be 1, 0, t, f, true, false, TRUE, FALSE, True, False.\n  */\n+package flag
 \n import (\n \t\"fmt\";\n+\t\"os\";\n \t\"strconv\"\n )\
 \n@@ -186,13 +186,14 @@ func (s *stringValue) String() string {\n \treturn fmt.Sprintf(\"%s\", *s.p)\n }\n \n-// -- FlagValue interface\n+// FlagValue is the interface to the dynamic value stored in a flag.\n+// (The default value is represented as a string.)\n type FlagValue interface {\n \tString() string;\n \tset(string) bool;\n }\n \n-// -- Flag structure\n+// A Flag represents the state of a flag.\n type Flag struct {\n \tName\tstring;\t// name as it appears on command line\n \tUsage\tstring;\t// help message\n@@ -208,20 +209,21 @@ type allFlags struct {\n \n var flags *allFlags = &allFlags{make(map[string] *Flag), make(map[string] *Flag), 1}\n \n-// Visit all flags, including those defined but not set.\n+// VisitAll visits the flags, calling fn for each. It visits all flags, even those not set.\n func VisitAll(fn func(*Flag)) {\n \tfor k, f := range flags.formal {\n \t\tfn(f)\n \t}\n }\n \n-// Visit only those flags that have been set\n+// Visit visits the flags, calling fn for each. It visits only those flags that have been set.\n func Visit(fn func(*Flag)) {\n \tfor k, f := range flags.actual {\n \t\tfn(f)\n \t}\n }\n \n+// Lookup returns the Flag structure of the named flag, returning nil if none exists.\n func Lookup(name string) *Flag {\n \tf, ok := flags.formal[name];\n \tif !ok {\n@@ -230,6 +232,8 @@ func Lookup(name string) *Flag {\n \treturn f\n }\n \n+// Set sets the value of tne named flag.  It returns true if the set succeeded; false if\n+// there is no such flag defined.\n func Set(name, value string) bool {\n \tf, ok := flags.formal[name];\n \tif !ok {\n@@ -243,6 +247,7 @@ func Set(name, value string) bool {\n \treturn true;\n }\n \n+// PrintDefaults prints to standard error the default values of all defined flags.\n func PrintDefaults() {\n \tVisitAll(func(f *Flag) {\n \t\tformat := \"  -%s=%s: %s\\n\";\n@@ -250,15 +255,17 @@ func PrintDefaults() {\n \t\t\t// put quotes on the value\n \t\t\tformat = \"  -%s=%q: %s\\n\";\n \t\t}\n-\t\tfmt.Printf(format, f.Name, f.DefValue, f.Usage);\n+\t\tfmt.Fprintf(os.Stderr, format, f.Name, f.DefValue, f.Usage);\n \t})\n }\n \n+// Usage prints to standard error a default usage message documenting all defined flags and\n+// then calls sys.Exit(1).\n func Usage() {\n \tif len(sys.Args) > 0 {\n-\t\tprint(\"Usage of \", sys.Args[0], \": \\n\");\n+\t\tfmt.Fprintf(os.Stderr, \"Usage of \", sys.Args[0], \": \\n\");\n \t} else {\n-\t\tprint(\"Usage: \\n\");\n+\t\tfmt.Fprintf(os.Stderr, \"Usage: \\n\");\n \t}\n \tPrintDefaults();\n \tsys.Exit(1);\n@@ -268,6 +275,8 @@ func NFlag() int {\n \treturn len(flags.actual)\n }\n \n+// Arg returns the i\'th command-line argument.  Arg(0) is the first remaining argument\n+// after flags have been processed.\n func Arg(i int) string {\n \ti += flags.first_arg;\n \tif i < 0 || i >= len(sys.Args) {\n@@ -276,6 +285,7 @@ func Arg(i int) string {\n \treturn sys.Args[i]\n }\n \n+// NArg is the number of arguments remaining after flags have been processed.\n func NArg() int {\n \treturn len(sys.Args) - flags.first_arg\n }\n@@ -291,60 +301,84 @@ func add(name string, value FlagValue, usage string) {\n \tflags.formal[name] = f;\n }\n \n+// BoolVar defines a bool flag with specified name, default value, and usage string.\n+// The argument p points to a bool variable in which to store the value of the flag.\n func BoolVar(p *bool, name string, value bool, usage string) {\n \tadd(name, newBoolValue(value, p), usage);\n }\n \n+// Bool defines a bool flag with specified name, default value, and usage string.\n+// The return value is the address of a bool variable that stores the value of the flag.\n func Bool(name string, value bool, usage string) *bool {\n \tp := new(bool);\n \tBoolVar(p, name, value, usage);\n \treturn p;\n }\n \n+// IntVar defines an int flag with specified name, default value, and usage string.\n+// The argument p points to an int variable in which to store the value of the flag.\n func IntVar(p *int, name string, value int, usage string) {\n \tadd(name, newIntValue(value, p), usage);\n }\n \n+// Int defines an int flag with specified name, default value, and usage string.\n+// The return value is the address of an int variable that stores the value of the flag.\n func Int(name string, value int, usage string) *int {\n \tp := new(int);\n \tIntVar(p, name, value, usage);\n \treturn p;\n }\n \n+// Int64Var defines an int64 flag with specified name, default value, and usage string.\n+// The argument p points to an int64 variable in which to store the value of the flag.\n func Int64Var(p *int64, name string, value int64, usage string) {\n \tadd(name, newInt64Value(value, p), usage);\n }\n \n+// Int64 defines an int64 flag with specified name, default value, and usage string.\n+// The return value is the address of an int64 variable that stores the value of the flag.\n func Int64(name string, value int64, usage string) *int64 {\n \tp := new(int64);\n \tInt64Var(p, name, value, usage);\n \treturn p;\n }\n \n+// UintVar defines a uint flag with specified name, default value, and usage string.\n+// The argument p points to a uint variable in which to store the value of the flag.\n func UintVar(p *uint, name string, value uint, usage string) {\n \tadd(name, newUintValue(value, p), usage);\n }\n \n+// Uint defines a uint flag with specified name, default value, and usage string.\n+// The return value is the address of a uint variable that stores the value of the flag.\n func Uint(name string, value uint, usage string) *uint {\n \tp := new(uint);\n \tUintVar(p, name, value, usage);\n \treturn p;\n }\n \n+// Uint64Var defines a uint64 flag with specified name, default value, and usage string.\n+// The argument p points to a uint64 variable in which to store the value of the flag.\n func Uint64Var(p *uint64, name string, value uint64, usage string) {\n \tadd(name, newUint64Value(value, p), usage);\n }\n \n+// Uint64 defines a uint64 flag with specified name, default value, and usage string.\n+// The return value is the address of a uint64 variable that stores the value of the flag.\n func Uint64(name string, value uint64, usage string) *uint64 {\n \tp := new(uint64);\n \tUint64Var(p, name, value, usage);\n \treturn p;\n }\n \n+// StringVar defines a string flag with specified name, default value, and usage string.\n+// The argument p points to a string variable in which to store the value of the flag.\n func StringVar(p *string, name, value string, usage string) {\n \tadd(name, newStringValue(value, p), usage);\n }\n \n+// String defines a string flag with specified name, default value, and usage string.\n+// The return value is the address of a string variable that stores the value of the flag.\n func String(name, value string, usage string) *string {\n \tp := new(string);\n \tStringVar(p, name, value, usage);\n@@ -430,6 +464,8 @@ func (f *allFlags) parseOne(index int) (ok bool, next int)\n \treturn true, index + 1\n }\n \n+// Parse parses the command-line flags.  Must be called after all flags are defined\n+// and before any are accessed by the program.\n func Parse() {\n \tfor i := 1; i < len(sys.Args); {\n \t\tok, next := flags.parseOne(i);\n```

## コアとなるコードの解説

### `src/lib/Makefile`の変更

`flag.6: fmt.dirinstall os.dirinstall strconv.dirinstall`という行は、`flag`パッケージのビルドに必要な依存関係を定義しています。以前は`fmt.dirinstall`のみでしたが、このコミットで`os.dirinstall`と`strconv.dirinstall`が追加されました。これは、`flag.go`のコードが`os`パッケージ(特に`os.Stderr`)と`strconv`パッケージ(数値と文字列の変換のため)に依存するようになったことをビルドシステムに伝えています。これにより、`flag`パッケージが正しくビルドされることが保証されます。

### `src/lib/flag.go`の変更

1.  **`package flag`の位置変更と`os`パッケージのインポート**:
    `package flag`の宣言が、ファイル冒頭のライセンスコメントブロックの直後から、その下のドキュメントコメントブロックの後に移動しました。これは主にコードのスタイルと可読性に関する変更です。
    また、`import ("fmt"; "os"; "strconv")`のように、`os`パッケージが新たにインポートされました。これは、後述する標準エラー出力への変更のために`os.Stderr`を使用するためです。

2.  **ドキュメントコメントの追加**:
    `FlagValue`インターフェース、`Flag`構造体、そして`VisitAll`, `Visit`, `Lookup`, `Set`, `PrintDefaults`, `Usage`, `Arg`, `NArg`, `BoolVar`, `Bool`, `IntVar`, `Int`, `Int64Var`, `Int64`, `UintVar`, `Uint`, `Uint64Var`, `Uint64`, `StringVar`, `String`, `Parse`といった主要な関数やメソッドに、詳細なドキュメントコメントが追加されました。これらのコメントは、各要素の目的、引数、戻り値、使用例などを説明しており、`godoc`ツールで生成されるドキュメントの質を大幅に向上させます。例えば、`FlagValue`インターフェースには「FlagValue is the interface to the dynamic value stored in a flag.」という説明が、`PrintDefaults`関数には「PrintDefaults prints to standard error the default values of all defined flags.」という説明が追加されています。

3.  **出力先を標準エラー出力へ変更**:
    `PrintDefaults()`関数と`Usage()`関数内の出力処理が変更されました。
    *   **`PrintDefaults()`**:
        変更前: `fmt.Printf(format, f.Name, f.DefValue, f.Usage);`
        変更後: `fmt.Fprintf(os.Stderr, format, f.Name, f.DefValue, f.Usage);`
        これにより、定義されているフラグのデフォルト値と使用方法に関する情報が、標準出力ではなく標準エラー出力に書き出されるようになりました。
    *   **`Usage()`**:
        変更前: `print("Usage of ", sys.Args[0], ": \\n");` および `print("Usage: \\n");`
        変更後: `fmt.Fprintf(os.Stderr, "Usage of ", sys.Args[0], ": \\n");` および `fmt.Fprintf(os.Stderr, "Usage: \\n");`
        同様に、プログラムの使用方法に関するメッセージも標準エラー出力に書き出されるようになりました。

これらの変更は、`flag`パッケージのAPIの明確化と、コマンドラインツールの出力に関するベストプラクティスへの準拠を目的としています。

## 関連リンク

*   Go言語 `flag` パッケージの公式ドキュメント (現在のバージョン):
    [https://pkg.go.dev/flag](https://pkg.go.dev/flag)
*   Go言語 `os` パッケージの公式ドキュメント:
    [https://pkg.go.dev/os](https://pkg.go.dev/os)
*   Go言語 `fmt` パッケージの公式ドキュメント:
    [https://pkg.go.dev/fmt](https://pkg.go.dev/fmt)

## 参考にした情報源リンク

*   [https://github.com/golang/go/commit/0ea27e345e006b177d8ffc7e86188accf871d0d0](https://github.com/golang/go/commit/0ea27e345e006b177d8ffc7e86188accf871d0d0) (本コミットのGitHubページ)
*   Go言語の初期開発に関する一般的な情報源(Go言語の歴史や設計思想に関する記事など)
*   Unix系OSにおける標準入出力に関する一般的な情報源(`stdout`, `stderr`の慣習に関する記事など)
*   Go言語の`godoc`ツールに関する情報源