HOWTO · Go
Go でシェルコマンドを実行する方法
Go から os/exec を使って外部プログラムを実行し、出力をキャプチャし、エラーを処理し、引数を渡し、コマンドの作業ディレクトリを設定する方法を学びます。シェルの機能を使うには、シェルを明示的に呼び出す必要があります。
このページの内容
Go から外部プログラムを実行するには、標準ライブラリの os/exec パッケージを使用します。これは名前付きの実行ファイルを直接起動し、シェルを暗黙的に実行することはしません。そのため、パイプ、リダイレクト、変数展開、ワイルドカードなどのシェル機能は適用されません。タスクにそれらの機能が必要な場合は、シェルを明示的に呼び出し、そのスクリプトを引数として渡してください。その際、信頼できないテキストをそのスクリプトに入れないよう注意してください。
os/exec でコマンドを実行する
exec.Command でコマンドを作成し、次に Output などのメソッドを呼び出してコマンドを開始し、その標準出力を収集します。最初の引数は実行ファイルを指定し、残りの引数はそのプログラムに渡されます。この完全なプログラムは、1 つの引数を持つ子プロセスとして自分自身を起動します。子は固定の行を出力し、親はその標準出力をキャプチャして自身の標準出力に書き込みます。プログラムは親プロセスを通じて子の行を出力します。
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
if len(os.Args) > 1 && os.Args[1] == "child" {
fmt.Println("hello from child")
return
}
cmd := exec.Command(os.Args[0], "child")
output, err := cmd.Output()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Print(string(output))
}
hello from child
ここでは、os.Args[0] が現在の実行ファイルを識別するため、この例は別のユーティリティがインストールされていることに依存しません。アプリケーションでは、これを git や go など必要な実行ファイルに置き換え、そのプログラムの引数を別々に指定してください。
出力をキャプチャし、エラーを処理する
結果を使用する前に、Output または CombinedOutput が返したエラーを確認してください。見つからない実行ファイルなど、プログラムの開始に失敗することと、プログラムが開始して 0 以外のステータスで終了することは異なります。後者の場合、エラーは通常 *exec.ExitError です。終了ステータスが重要な場合はこれを調べます。Cmd 型ではストリームを直接接続することもできます。出力をバイトスライスに収集する代わりにそこへ送りたい場合は、cmd.Stdout または cmd.Stderr にライターを設定してください。
プログラムが必要とするデータが標準出力である場合は Output を使用します。返されるバイトに標準エラーも必要な場合は CombinedOutput を使用します。2 つのストリームは結合されるため、それらの相対的な順序は別々のストリームを再構築する信頼できる方法ではありません。診断を別々に保つ必要がある場合は、cmd.Stdout と cmd.Stderr に別々のライターを割り当ててください。エラーは、呼び出し側が再試行するか、戻るか、続行するかを決定できる時点で処理し、報告してください。エラーを出力してからコマンドが成功したものとして扱うと、失敗した操作が隠れてしまうことがあります。
コマンドに引数を渡す
各引数は、実行ファイル名の後に独自の文字列として渡します。たとえば exec.Command("grep", "-n", pattern, filename) です。Go はこれらの値をシェルコマンドに結合したり、スペース、引用符、パイプ、リダイレクト文字を解釈したりすることはありません。スペースを含むファイル名は、1 つの文字列として指定すれば 1 つの引数のままです。これによりシェルの解析は避けられますが、呼び出されるプログラムに対する入力が検証されるわけではありません。引数はそのプログラムにとって特別な意味を持つ場合があるため、コマンド自体のインターフェースに従って値を検証してください。
入力ストリームと出力ストリームについては、cmd.Stdin に io.Reader を、cmd.Stdout または cmd.Stderr にライターを割り当ててから Run を呼び出します。上記のサンプル実行ファイルは、短い結果をキャプチャするだけなので Output を使用しています。ストリームを接続し、標準出力を戻り値として収集する必要がない場合は Run を使用してください。実行ファイルはプロセスから利用可能でなければならず、そのオプションはオペレーティングシステムによって異なる場合があります。
別のディレクトリでコマンドを実行する
コマンドの作業ディレクトリを選択するには、コマンドを開始する前に cmd.Dir を設定します。たとえば、コマンドを構築した後、cmd.Dir = projectDir を設定し、次に Output、CombinedOutput、または Run を呼び出します。ディレクトリは存在し、プロセスがアクセスできる必要があります。相対パスの cmd.Dir は、Go プロセスの現在の作業ディレクトリを基準に解決されます。呼び出し側の開始ディレクトリが変わる可能性がある場合は、絶対パスを使用してください。この設定は子プロセスのディレクトリを変更し、Go プロセス自体の作業ディレクトリは変更しません。