283
191

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-03-08

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的なことができます。

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
283
191
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
283
191

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?