tkuchikiの日記

新ブログ https://blog.tkuchiki.net

golang で外部コマンドを実行して標準出力を取得する

執筆時に使用した go version は、go1.3 です。

golang で外部コマンドを実行して、
標準出力の結果を受け取る方法です。
Go Playground では試すことができませんのでご注意ください。

※追記1

id:mattn さんにご指摘いただいた、Cmd.Output の例を追記しました。
標準出力だけ取れれば良いなら、非常に簡潔に書くことができます。
ありがとうございました。

※追記2

ドキュメントを読みなおしたところ、
Cmd.CombinedOutput という、
標準出力と標準エラー出力両方とれるものがありましたので、
追記しました。

※追記3

最初に書いた標準出力一括取得は、それほど使わなさそうだと思いましたので、
削除して、末尾にStdinPipe の例を記載しました。

※追記4

id:id:hnakamur3 さんに教えていただきました記事へのリンクを追加しました 。
Goで外部コマンドを実行して出力をリアルタイム表示するサンプル - Qiita
StdoutPipe と StderrPipe は、
両方同時に取得したい場合やリアルタイムに取得したい場合に使うと良さそうですね。

※追記5

id:mattn さんが、twitter で StdoutPipe を使わないでCmd.Stdout に io.Writer を入れたほうが楽だとおっしゃっているのを見かけましたので、その例も追加しました。
仰るとおり、わかりやすかったです。

一括取得

標準出力限定版(Cmd.Output)
package main

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

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

        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }

        fmt.Println(string(out))
}
実行結果
total 8
drwxr-xr-x   3 tkuchiki  tkuchiki   102 11 10 12:01 .
drwxrwxrwt  53 tkuchiki  tkuchiki  1802 11 10 12:00 ..
-rw-r--r--   1 tkuchiki  tkuchiki   194 11 10 12:03 main.go
標準出力、標準エラー出力版(Cmd.CombinedOutput)

標準エラー出力を受ける場合は、
CombinedOutput の error の返り値が nil ではないので、

        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }

などとすると何も表示できないので注意してください。

package main

import (
        "fmt"
        "os/exec"
)

func main() {
        out, _ := exec.Command("ls", "-X").CombinedOutput()
        fmt.Println(string(out))

        out, _ = exec.Command("ls", "-al").CombinedOutput()
        fmt.Println(string(out))
}
実行結果
ls: illegal option -- X
usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]

total 8
drwxr-xr-x   3 tkuchiki  tkuchiki   102 11 10 12:01 .
drwxrwxrwt  53 tkuchiki  tkuchiki  1802 11 10 12:00 ..
-rw-r--r--   1 tkuchiki  tkuchiki   194 11 10 12:03 main.go
StdoutPipe を使わない版
package main

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

func main() {
        cmd := exec.Command("ls", "-al")
        var stdout bytes.Buffer
        cmd.Stdout = &stdout

        err := cmd.Run()

        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }

        fmt.Println(stdout.String())
}
実行結果
total 8
drwxr-xr-x   3 tkuchiki  tkuchiki   102 11 10 12:01 .
drwxrwxrwt  53 tkuchiki  tkuchiki  1802 11 10 12:00 ..
-rw-r--r--   1 tkuchiki  tkuchiki   259 11 10 12:03 main.go

1行ずつ取得

code
package main

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

func main() {
	cmd := exec.Command("ls", "-al")
	stdout, err := cmd.StdoutPipe()

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	cmd.Start()

	scanner := bufio.NewScanner(stdout)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
		fmt.Println()
	}

	cmd.Wait()
}
実行結果
total 8

drwxr-xr-x   3 tkuchiki  tkuchiki   102 11 10 12:01 .

drwxrwxrwt  53 tkuchiki  tkuchiki  1802 11 10 12:00 ..

-rw-r--r--   1 tkuchiki  tkuchiki   331 11 10 12:01 main.go

一括取得と1行ずつ取得した場合の違いをわかりやすくするために、
1行ずつ取得する方では、改行を1つ追加して出力しています。

最後に StdinPipe の例を載せておきます。

標準入力版
package main

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

func main() {
        cmd := exec.Command("wc", "-w")
        stdin, err := cmd.StdinPipe()

        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }

        io.WriteString(stdin, "hoge foo bar")
        stdin.Close()
        out, err := cmd.Output()

        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }

        fmt.Println(string(out))
}
実行結果
       3