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

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

このコミットは、Go言語のコマンドラインツール go run において、実行されるプログラムが標準入力 (os.Stdin) にアクセスできるようにするための変更です。これにより、go run で実行されるGoプログラムが、ユーザーからの入力やパイプからのデータを受け取れるようになります。

コミット

commit 878608bd29b936b889e130b0bb81cfbc523ae233
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed Feb 8 16:30:28 2012 +1100

    cmd/go: connect os.Stdin for go run
    
    Fixes #2875
    
    R=golang-dev, r, rsc
    CC=golang-dev
    https://golang.org/cl/5641052

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

https://github.com/golang/go/commit/878608bd29b936b889e130b0bb81cfbc523ae233

元コミット内容

このコミットは、cmd/go パッケージ、具体的には go run コマンドの動作を修正するものです。以前の go run は、実行されるGoプログラムの標準入力 (os.Stdin) を適切に接続していませんでした。このため、fmt.Scanln() のような標準入力からデータを読み取る関数を使用するプログラムは、go run 経由で実行された場合に期待通りに動作しませんでした。このコミットは、この問題を解決し、go run が実行するプログラムに標準入力を接続するように変更します。これは、Goの旧トラッカーにおけるIssue #2875を修正するものです。

変更の背景

Go言語の go run コマンドは、Goのソースファイルをコンパイルし、その場で実行するための便利なツールです。開発中に小さなスクリプトやテストコードを素早く実行する際によく利用されます。しかし、このコミットが導入される前は、go run が実行するプログラムは標準入力にアクセスできませんでした。これは、プログラムがユーザーからのインタラクティブな入力(例えば、コマンドラインからのプロンプト応答)を必要とする場合や、パイプを通じて他のコマンドからの入力を受け取る場合に問題となります。

例えば、以下のようなGoプログラムがあったとします。

package main

import (
	"fmt"
)

func main() {
	var name string
	fmt.Print("What is your name? ")
	fmt.Scanln(&name)
	fmt.Printf("Hello, %s!\n", name)
}

このプログラムを go run main.go で実行した場合、標準入力が接続されていないと、fmt.Scanln(&name) の部分でプログラムがハングアップするか、エラーが発生する可能性がありました。このコミットは、このようなユーザーエクスペリエンスの欠陥を修正し、go run がより汎用的に使えるようにすることを目的としています。

前提知識の解説

1. go run コマンド

go run は、Go言語のソースファイルをコンパイルし、その場で実行するコマンドです。通常、go build で実行可能ファイルを生成してから実行する手間を省き、開発のイテレーションを高速化します。内部的には、一時ディレクトリにコンパイルされたバイナリを生成し、それを実行しています。

2. 標準入出力 (Standard I/O)

コンピュータプログラムは、通常、以下の3つの標準ストリームを通じて外部と通信します。

  • 標準入力 (Stdin): プログラムがデータを読み取るための入力ストリーム。通常はキーボードからの入力や、パイプ (|) を介して他のプログラムの出力が接続されます。Go言語では os.Stdin で表現されます。
  • 標準出力 (Stdout): プログラムが通常の結果を出力するためのストリーム。通常はターミナルに表示されます。Go言語では os.Stdout で表現されます。
  • 標準エラー出力 (Stderr): プログラムがエラーメッセージや診断情報を出力するためのストリーム。通常はターミナルに表示されますが、標準出力とは別にリダイレクトできます。Go言語では os.Stderr で表現されます。

これらのストリームは、Unix系のオペレーティングシステムにおけるファイルディスクリプタ (0: Stdin, 1: Stdout, 2: Stderr) に対応しています。

3. os/exec パッケージ

Go言語の os/exec パッケージは、外部コマンドを実行するための機能を提供します。このパッケージを使用すると、Goプログラム内からシェルコマンドや他の実行可能ファイルを起動し、その入出力を制御することができます。

  • exec.Command(name string, arg ...string): 実行するコマンドと引数を指定して Cmd 構造体を作成します。
  • Cmd.Stdin, Cmd.Stdout, Cmd.Stderr: Cmd 構造体のフィールドで、実行される外部コマンドの標準入出力ストリームを設定するために使用されます。これらに os.Stdin, os.Stdout, os.Stderr を割り当てることで、親プロセス(この場合は go run コマンド自身)の標準入出力を子プロセス(実行されるGoプログラム)に引き継ぐことができます。
  • Cmd.Run(): コマンドを実行し、完了するまで待機します。エラーが発生した場合は error を返します。

技術的詳細

このコミットの核心は、go run コマンドが内部でGoプログラムを実行する際に、その子プロセスの標準入力を親プロセスの標準入力に接続することです。これは、os/exec パッケージの Cmd 構造体を利用して実現されます。

以前の go run の実装では、実行されるプログラムの標準入力が明示的に設定されていなかったため、デフォルトの動作(通常は /dev/null から読み取るか、単に閉じられている状態)になっていました。このコミットでは、runStdin という新しいヘルパー関数を導入し、この関数内で exec.Command を使用して子プロセスを起動し、その Stdin フィールドに os.Stdin を割り当てています。同様に、StdoutStderros.Stdoutos.Stderr に接続することで、子プロセスの出力が親プロセスの出力に直接流れるようにしています。

これにより、go run で実行されるGoプログラムは、あたかも直接コマンドラインから実行されたかのように、キーボードからの入力やパイプからのデータを受け取ることができるようになります。

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

変更は src/cmd/go/run.go ファイルに集中しています。

--- a/src/cmd/go/run.go
+++ b/src/cmd/go/run.go
@@ -7,6 +7,7 @@ package main
 import (
 	"fmt"
 	"os"
+	"os/exec" // 新しくインポートされたパッケージ
 	"strings"
 )
 
@@ -61,6 +62,19 @@ func (b *builder) runProgram(a *action) error {
 			return nil
 		}
 	}\n
-	run(a.deps[0].target, a.args) // 変更前: run 関数を呼び出し
+	runStdin(a.deps[0].target, a.args) // 変更後: runStdin 関数を呼び出し
 	return nil
 }
+\n
+// runStdin is like run, but connects Stdin.
+// runStdin は run と似ていますが、Stdin を接続します。
+func runStdin(cmdargs ...interface{}) {
+	cmdline := stringList(cmdargs...)
+	cmd := exec.Command(cmdline[0], cmdline[1:]...)
+	cmd.Stdin = os.Stdin // 標準入力を接続
+	cmd.Stdout = os.Stdout // 標準出力を接続
+	cmd.Stderr = os.Stderr // 標準エラー出力を接続
+	if err := cmd.Run(); err != nil {
+		errorf("%v", err)
+	}
+}

コアとなるコードの解説

  1. os/exec パッケージのインポート: import "os/exec" が追加され、外部コマンドの実行と制御に必要な機能が利用可能になりました。

  2. runProgram 関数の変更: runProgram 関数は、go run コマンドの実行ロジックの中心となる部分です。以前は run(a.deps[0].target, a.args) を呼び出していましたが、このコミットにより runStdin(a.deps[0].target, a.args) に変更されました。これは、標準入力を接続する新しいヘルパー関数 runStdin を使用するように切り替えたことを意味します。

  3. runStdin 関数の新規追加: この関数がコミットの主要な変更点です。

    • cmdline := stringList(cmdargs...): 実行するコマンドとその引数を文字列スライスに変換します。
    • cmd := exec.Command(cmdline[0], cmdline[1:]...): os/exec パッケージの Command 関数を使用して、実行する外部コマンド (cmdline[0]) とその引数 (cmdline[1:]...) を指定し、Cmd 構造体のインスタンスを作成します。
    • cmd.Stdin = os.Stdin: ここが最も重要な変更点です。新しく作成された Cmd オブジェクトの Stdin フィールドに、親プロセス(go run コマンド自身)の標準入力である os.Stdin を割り当てています。これにより、子プロセスは親プロセスと同じ標準入力ストリームを使用するようになります。
    • cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderr: 同様に、標準出力と標準エラー出力も親プロセスに接続しています。これにより、子プロセスの出力が直接ターミナルに表示されるようになります。
    • if err := cmd.Run(); err != nil { errorf("%v", err) }: cmd.Run() を呼び出して外部コマンドを実行します。コマンドの実行中にエラーが発生した場合は、errorf 関数(Goコマンドラインツール内でエラーを出力するためのヘルパー関数)を使用してエラーメッセージを表示します。

この変更により、go run は実行されるGoプログラムに対して、完全な標準入出力の環境を提供するようになり、より多くの種類のGoプログラムを go run で直接実行できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語のIssueトラッカー (旧Google Code): 2012年当時のGoのIssueトラッカーはGoogle Codeでホストされていましたが、現在はアーカイブされており、直接アクセスすることは困難です。しかし、このコミットメッセージに記載されている Fixes #2875 は、当時のGoプロジェクトのIssueトラッカーの特定の課題を指しています。
  • Go言語のコードレビューシステム (Gerrit): https://golang.org/cl/5641052 は、Goプロジェクトが使用しているGerritコードレビューシステムのチェンジリストへのリンクです。このリンクは現在も有効であり、このコミットの詳細なレビュープロセスや議論を確認できます。