[インデックス 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
を割り当てています。同様に、Stdout
と Stderr
も os.Stdout
と os.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)
+ }
+}
コアとなるコードの解説
-
os/exec
パッケージのインポート:import "os/exec"
が追加され、外部コマンドの実行と制御に必要な機能が利用可能になりました。 -
runProgram
関数の変更:runProgram
関数は、go run
コマンドの実行ロジックの中心となる部分です。以前はrun(a.deps[0].target, a.args)
を呼び出していましたが、このコミットによりrunStdin(a.deps[0].target, a.args)
に変更されました。これは、標準入力を接続する新しいヘルパー関数runStdin
を使用するように切り替えたことを意味します。 -
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.Stdout
とcmd.Stderr = os.Stderr
: 同様に、標準出力と標準エラー出力も親プロセスに接続しています。これにより、子プロセスの出力が直接ターミナルに表示されるようになります。if err := cmd.Run(); err != nil { errorf("%v", err) }
:cmd.Run()
を呼び出して外部コマンドを実行します。コマンドの実行中にエラーが発生した場合は、errorf
関数(Goコマンドラインツール内でエラーを出力するためのヘルパー関数)を使用してエラーメッセージを表示します。
この変更により、go run
は実行されるGoプログラムに対して、完全な標準入出力の環境を提供するようになり、より多くの種類のGoプログラムを go run
で直接実行できるようになりました。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
os/exec
パッケージのドキュメント: https://pkg.go.dev/os/execos
パッケージのドキュメント: https://pkg.go.dev/os
参考にした情報源リンク
- 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コードレビューシステムのチェンジリストへのリンクです。このリンクは現在も有効であり、このコミットの詳細なレビュープロセスや議論を確認できます。- https://go.dev/cl/5641052 (リダイレクト後のURL)