Edited at

Golangで外部コマンドを実行する方法まとめ

More than 1 year has passed since last update.

Golangはos/execパッケージを使用して外部コマンドを実行できます。


基本

処理したいコマンドをexec.Command()に渡します。

第一引数がコマンド、第二引数以降(可変長)はコマンドオプションです。

https://golang.org/pkg/os/exec/#Command


コマンドを実行する(結果を取得しない)

単に実行するだけであれば、.Run()を使います。

.Run()の場合、コマンドの実行が完了するまで待ちます。

err := exec.Command("ls", "-la").Run()


コマンドを実行して結果を取得する

実行結果も取得したい場合は.Output()を使います。

こちらも.Run()同様、コマンドの完了を待ちます。

out, err := exec.Command("ls", "-la").Output()


コマンドを実行するが、完了を待たない

完了を待たなくて良い場合は、.Start()を使います。

.Start()で開始したコマンドの終了を待ちたい時は、後述の.Wait()を使います。

err := exec.Command("nc", "-l").Start()


コマンドの完了を待つ

.Start()で開始したコマンドの終了を待つ場合、.Wait()を使います。

下記はsleep 5s.Start()した後に.Wait()するサンプルです。

package main

import (
"fmt"
"os/exec"
"time"
)

func main() {
fmt.Println("処理開始: ", time.Now().Format("15:04:05"))
cmd := exec.Command("sleep", "5s")
cmd.Start()
fmt.Println("sleep中: ", time.Now().Format("15:04:05"))
cmd.Wait()
fmt.Println("sleep終了: ", time.Now().Format("15:04:05"))
}

実行結果

処理開始:  19:23:16

sleep中: 19:23:16
sleep終了: 19:23:21

.Wait()で5秒待ってくれてますね。


標準入力を渡す

.StdinPipe()を使うとコマンドに標準入力を渡せます。

package main

import (
"fmt"
"io"
"os/exec"
)

func main() {
cmd := exec.Command("wc")
stdin, _ := cmd.StdinPipe()
io.WriteString(stdin, "hoge")
stdin.Close()
out, _ := cmd.Output()
fmt.Printf("結果: %s", out)
}

実行結果

結果:        0       1       4


パイプやリダイレクトを使う

パイプやリダイレクトは直接exec.Command()に渡せないので、sh -cの引数として渡します。

cmdstr := "ip route | grep default"

out, err := exec.Command("sh", "-c", cmdstr).Output()

ただし、上記の方法だと環境変数まわりで事故る可能性があるので、安全にやりたい方はgo-pipeline使うのがおすすめです。


コマンド文字列をexec.Commandの引数として渡したい

コマンドとオプションが一緒に格納されている文字列をexec.Command()に渡したい場合、mattnさん作のgo-shellwordsを使うと便利です。

下記の関数は、コマンドとオプションが入った文字列をgo-shellwordsでパース(スライス化)してexec.Command().Run()を呼び出すサンプルです。

func runCmdStr(cmdstr string) error {

// 文字列をコマンド、オプション単位でスライス化する
c, err := shellwords.Parse(cmdstr)
if err != nil {
return err
}
switch len(c) {
case 0:
// 空の文字列が渡された場合
return nil
case 1:
// コマンドのみを渡された場合
err = exec.Command(c[0]).Run()
default:
// コマンド+オプションを渡された場合
// オプションは可変長でexec.Commandに渡す
err = exec.Command(c[0], c[1:]...).Run()
}
if err != nil {
return err
}
return nil
}


実行している(した)コマンドのPIDを取得する

PIDは、CmdProcess.Pidで参照できます。

コマンド実行前に参照するとpanic起こすので注意。

また、Process配下のメソッドでプロセスをkillしたりシグナル送ったりできます。

参考:https://golang.org/pkg/os/#Process

package main

import (
"fmt"
"os/exec"
)

func main() {
cmd := exec.Command("bash")
cmd.Start()
fmt.Println("実行中: ", cmd.Process.Pid)
cmd.Process.Kill()
cmd.Wait()
fmt.Println("Kill後: ", cmd.Process.Pid)
}

実行結果

実行中:  19558

Kill後: 19558


実行ファイルのパスを知りたい

exec.LookPathを使うとwhich的なことができます。

https://golang.org/pkg/os/exec/#LookPath

package main

import (
"fmt"
"log"
"os/exec"
)

func main() {
path, err := exec.LookPath("go")
if err != nil {
log.Fatal("not found")
}
fmt.Printf("go is available at %s\n", path)
}

実行結果

go is available at /usr/local/go/bin/go