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

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

このコミットは、Goコマンドラインツール (cmd/go) の go env サブコマンドの出力形式を、Plan 9オペレーティングシステムの rc シェルと互換性があるように修正するものです。特に、GOPATH のように複数のパスコンポーネントを持つ環境変数の表現が改善され、Plan 9環境でのGo開発の利便性が向上しています。

コミット

commit ff15e5c00f7fe3fcec1277f932fbca381fd2d2ad
Author: Lucio De Re <lucio.dere@gmail.com>
Date:   Mon Feb 24 19:48:06 2014 +0100

    cmd/go: Plan 9 compatible "env" output
    
    Fixes the output of go env so that variables can be set
    more accurately when using Plan 9's rc shell. Specifically,
    GOPATH may have multiple components and the current
    representation is plain wrong. In practice, we probably
    ought to change os. Getenv to produce the right result, but
    that requires considerably more thought.
    
    LGTM=rsc
    R=golang-codereviews, gobot, rsc
    CC=golang-codereviews
    https://golang.org/cl/66600043

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

https://github.com/golang/go/commit/ff15e5c00f7fe3fcec1277f932fbca381fd2d2ad

元コミット内容

    cmd/go: Plan 9 compatible "env" output
    
    Fixes the output of go env so that variables can be set
    more accurately when using Plan 9's rc shell. Specifically,
    GOPATH may have multiple components and the current
    representation is plain wrong. In practice, we probably
    ought to change os. Getenv to produce the right result, but
    that requires considerably more thought.

変更の背景

go env コマンドは、Goの環境変数の現在の設定を表示するために使用されます。このコマンドは、様々なシェル(bash, cmd, powershellなど)に対応した形式で出力を生成できます。しかし、従来の go env のPlan 9 (rc シェル) 向けの出力は、GOPATH のように複数のパスがコロン (:) で区切られて設定される環境変数に対して、正しく機能しないという問題がありました。

Plan 9の rc シェルでは、複数の値をリストとして持つ環境変数を表現する際に、特定の構文(例: VAR=(val1 val2 val3))を使用します。従来の go env の出力では、GOPATH のような変数が単一の引用符で囲まれた文字列として出力されていたため、rc シェルがこれを単一のパスとして解釈してしまい、Goツールが期待する複数のパスとして認識できませんでした。

このコミットは、この問題を解決し、go env がPlan 9の rc シェルで正しく環境変数を設定できるようにすることを目的としています。特に、GOPATH のようなパスリストを、rc シェルが正しく解釈できる形式で出力するように変更されています。コミットメッセージには、「os.Getenv を変更して正しい結果を生成すべきだが、それはより多くの検討が必要」という言及があり、これは一時的な修正であり、より根本的な解決策が将来的に検討される可能性を示唆しています。

前提知識の解説

  • Go言語の環境変数: Go言語のツールチェーンは、GOPATH, GOROOT, GOOS, GOARCH など、様々な環境変数に依存して動作します。これらの変数は、Goのソースコードの場所、ビルドターゲットのOSやアーキテクチャなどを指定します。
  • GOPATH: Goのワークスペースのルートディレクトリを指定する環境変数です。Go 1.11以降のGo Modulesの導入によりその重要性は低下しましたが、それ以前のバージョンや特定のレガシープロジェクトでは依然として重要な役割を果たします。複数のディレクトリをコロン (:) で区切って指定することができ、Goツールはこれらのディレクトリ内でソースコード、パッケージ、実行可能ファイルを検索します。
  • go env コマンド: Goツールチェーンの一部であり、Goの環境変数の現在の設定を表示するコマンドです。特定のシェル形式 (-s フラグで指定) で出力することも可能です。
  • Plan 9: ベル研究所で開発された分散オペレーティングシステムです。Unixの概念をさらに推し進め、すべてのリソースをファイルとして表現するという思想を持っています。
  • Plan 9 rc シェル: Plan 9の標準シェルです。Unix系のシェル(bash, zshなど)とは異なる独自の構文と哲学を持っています。特に、環境変数の扱い方やリストの表現方法に特徴があります。rc シェルでは、スペース区切りの要素を括弧で囲むことでリストを表現します。例: path=(/bin /usr/bin)
  • \x00 (NULLバイト): プログラミングにおいて、文字列の終端を示すためによく使われる特殊な文字です。Goの内部では、GOPATH のような複数のパスを持つ環境変数を表現するために、パスの各要素をNULLバイトで区切って格納することがあります。これは、OSの環境変数として設定される際にはコロンなどの区切り文字に変換されますが、内部表現ではNULLバイトが使われることがあります。

技術的詳細

このコミットの核心は、src/cmd/go/env.go ファイル内の runEnv 関数におけるPlan 9 (rc シェル) 向けの出力ロジックの変更です。

変更前は、Plan 9向けの出力は単純に fmt.Printf("%s='%s'\\n", e.name, strings.Replace(e.value, "'", "''", -1)) のように、環境変数の値を単一の引用符で囲んで出力していました。これは、GOPATH のように複数のパスがコロンで区切られた文字列として格納されている場合、rc シェルがこれを単一の文字列として解釈してしまい、期待通りの動作をしませんでした。

変更後は、以下のロジックが追加されました。

  1. NULLバイトのチェック: strings.IndexByte(e.value, '\x00') < 0 という条件で、環境変数の値 (e.value) にNULLバイト (\x00) が含まれているかどうかをチェックします。
    • Goの内部では、GOPATH のような複数のパスを持つ環境変数は、各パスがNULLバイトで区切られた単一の文字列として表現されることがあります。このチェックは、その内部表現を検出するためのものです。
  2. NULLバイトがない場合: NULLバイトが含まれていない場合(通常の単一値の環境変数など)は、変更前と同様に単一の引用符で囲まれた文字列として出力します。fmt.Printf("%s='%s'\\n", e.name, strings.Replace(e.value, "'", "''", -1))
  3. NULLバイトがある場合: NULLバイトが含まれている場合(GOPATH のようなパスリスト)は、値をNULLバイトで分割し、rc シェルのリスト構文 (VAR=(val1 val2 val3)) で出力します。
    • v := strings.Split(e.value, "\x00") で、NULLバイトを区切り文字として文字列をスライスに分割します。
    • fmt.Printf("%s=(", e.name) で、変数名と開き括弧を出力します。
    • ループ for x, s := range v で、分割された各パス要素を処理します。
    • if x > 0 { fmt.Printf(" ") } で、最初の要素以外はスペースを挿入し、各要素をスペースで区切ります。
    • fmt.Printf("%s", s) で、各パス要素を出力します。
    • fmt.Printf(")\\n") で、閉じ括弧と改行を出力します。

この変更により、GOPATH=/path/to/go/src:/path/to/another/src のような値が、Plan 9の rc シェルで GOPATH=(/path/to/go/src /path/to/another/src) のように正しく解釈される形式で出力されるようになります。

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

src/cmd/go/env.go ファイルの runEnv 関数内、case "plan9": ブロックが変更されています。

--- a/src/cmd/go/env.go
+++ b/src/cmd/go/env.go
@@ -91,7 +91,19 @@ func runEnv(cmd *Command, args []string) {
 			default:
 				fmt.Printf("%s=\"%s\"\\n\", e.name, e.value)
 			case "plan9":
-				fmt.Printf("%s='%s'\\n\", e.name, strings.Replace(e.value, "'", "''", -1))
+				if strings.IndexByte(e.value, '\x00') < 0 {
+					fmt.Printf("%s='%s'\\n\", e.name, strings.Replace(e.value, "'", "''", -1))
+				} else {
+					v := strings.Split(e.value, "\x00")
+					fmt.Printf("%s=(", e.name)
+					for x, s := range v {
+						if x > 0 {
+							fmt.Printf(" ")
+						}
+						fmt.Printf("%s", s)
+					}
+					fmt.Printf(")\\n")
+				}
 			case "windows":
 				fmt.Printf("set %s=%s\\n\", e.name, e.value)
 			}

コアとなるコードの解説

変更されたコードブロックは、go env コマンドがPlan 9 (rc シェル) 向けの出力を生成する際のロジックを定義しています。

  • e.name: 環境変数名(例: GOPATH
  • e.value: 環境変数の値(例: /home/user/go:/usr/local/go のような文字列、または内部的にはNULLバイトで区切られた文字列)

新しいロジックは、まず e.value にNULLバイト (\x00) が含まれているかどうかを確認します。

  • if strings.IndexByte(e.value, '\x00') < 0:

    • strings.IndexByte は、文字列 e.value 内で指定されたバイト (\x00) が最初に出現するインデックスを返します。見つからない場合は -1 を返します。
    • この条件は、e.value にNULLバイトが含まれていない場合(つまり、単一の値を表す環境変数である可能性が高い場合)に真となります。
    • この場合、以前と同様に fmt.Printf("%s='%s'\\n", e.name, strings.Replace(e.value, "'", "''", -1)) を実行します。これは、変数名をシングルクォートで囲まれた値として出力し、値に含まれるシングルクォートは二重にすることでエスケープします。
  • else ブロック:

    • e.value にNULLバイトが含まれている場合(つまり、GOPATH のように複数のパスがNULLバイトで区切られて格納されている場合)に実行されます。
    • v := strings.Split(e.value, "\x00"): e.value をNULLバイトで分割し、結果の文字列スライスを v に格納します。これにより、各パスが個別の要素として扱われます。
    • fmt.Printf("%s=(", e.name): rc シェルのリスト構文の開始部分 (VAR=() を出力します。
    • for x, s := range v: 分割されたパス要素 s をループ処理します。x はインデックスです。
    • if x > 0 { fmt.Printf(" ") }: 最初の要素以外の場合、要素間にスペースを挿入します。これは rc シェルのリスト構文で要素を区切るために必要です。
    • fmt.Printf("%s", s): 現在のパス要素 s を出力します。
    • fmt.Printf(")\\n"): rc シェルのリスト構文の終了部分 ()) と改行を出力します。

この変更により、go env はPlan 9の rc シェルに対して、単一値の環境変数は通常の文字列として、複数パスの環境変数はリストとして、それぞれ正しく解釈される形式で出力できるようになりました。

関連リンク

参考にした情報源リンク

  • コミットメッセージとコード差分 (git diff)
  • Go言語公式ドキュメント
  • Plan 9のドキュメント (特に rc シェルに関する情報)
  • Go言語の環境変数に関する一般的な知識
  • strings.IndexByte および strings.Split 関数のGo言語ドキュメント
  • Goの環境変数が内部的にNULLバイトで区切られることに関する情報 (Goのソースコードや関連する議論から)