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

  • 79
    Like
  • 0
    Comment
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