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