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は、Cmd
型のProcess.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