【golang】外部コマンドを叩き、標準出力・エラー出力をリアルタイムで拾う

# なにこれ
goでプロセス（外部コマンド）を起動し、そのプロセスの標準出力・エラー出力を
リアルタイムで追いかけるという事をします。goをバックエンドにプロセスを実行し、
その結果を利用して何かするってときに使えるかもしれません。


# 仕組み
- exec.Commandで指定したプロセスのstdout, stderrをgoroutineでさばく
- goroutineで拾ったデータをchannel経由で親に投げる

# プログラム

```go

package main

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

func main() {

        // コマンドと引数を定義する
        c := "go"
        p := []string{"run", "main.go"}
        cmd := exec.Command(c, p...)

        // 実行ディレクトリ
        cmd.Dir = "../test"

        // パイプを作る
        stdout, err := cmd.StdoutPipe()
        if err != nil {
                log.Fatal(err)
        }
        stderr, err := cmd.StderrPipe()
        if err != nil {
                log.Fatal(err)
        }

        err = cmd.Start()
        if err != nil {
                log.Fatal(err)
        }

        streamReader := func(scanner *bufio.Scanner, outputChan chan string, doneChan chan bool) {
                defer close(outputChan)
                defer close(doneChan)
                for scanner.Scan() {
                        outputChan <- scanner.Text()
                }
                doneChan <- true
        }

        // stdout, stderrをひろうgoroutineを起動
        stdoutScanner := bufio.NewScanner(stdout)
        stdoutOutputChan := make(chan string)
        stdoutDoneChan := make(chan bool)
        stderrScanner := bufio.NewScanner(stderr)
        stderrOutputChan := make(chan string)
        stderrDoneChan := make(chan bool)
        go streamReader(stdoutScanner, stdoutOutputChan, stdoutDoneChan)
        go streamReader(stderrScanner, stderrOutputChan, stderrDoneChan)

        // channel経由でデータを引っこ抜く
        stillGoing := true
        for stillGoing {
                select {
                case <-stdoutDoneChan:
                        stillGoing = false
                case line := <-stdoutOutputChan:
                        log.Println(line)
                case line := <-stderrOutputChan:
                        log.Println(line)
                }
        }

        //一応Waitでプロセスの終了をまつ
        ret := cmd.Wait()
        if ret != nil {
                log.Fatal(err)
        }
}
```

# 検証用スクリプト

以下のスクリプトは、1秒おきにstdout, stderrに時刻を投げるものである。
これを使って、上記スクリプトがに標準出力・エラー出力を拾えているか確認する。

```go
package main

import (
        "fmt"
        "os"
        "time"
)

func main() {
        ticker := time.NewTicker(1 * time.Second)
        stop := make(chan bool)
        go func() {
        loop:
                for {
                        select {
                        case t := <-ticker.C:
                                fmt.Fprintln(os.Stdout, "(STDOUT)Tick at", t)
                                fmt.Fprintln(os.Stderr, "(STDERR)Tick at", t)
                        case <-stop:
                                break loop
                        }
                }
                fmt.Println("Reachable!")
        }()

        time.Sleep(10 * time.Second)
        ticker.Stop()
        close(stop)
}
```

上記スクリプトの実行結果。

```bash

vagrant@main:~/test$ go run main.go
(STDOUT)Tick at 2017-01-09 11:31:59.881743758 +0000 UTC
(STDERR)Tick at 2017-01-09 11:31:59.881743758 +0000 UTC
(STDOUT)Tick at 2017-01-09 11:32:00.888469008 +0000 UTC
(STDERR)Tick at 2017-01-09 11:32:00.888469008 +0000 UTC
(STDOUT)Tick at 2017-01-09 11:32:01.87860581 +0000 UTC
(STDERR)Tick at 2017-01-09 11:32:01.87860581 +0000 UTC
(STDOUT)Tick at 2017-01-09 11:32:02.878606943 +0000 UTC
(STDERR)Tick at 2017-01-09 11:32:02.878606943 +0000 UTC
(STDOUT)Tick at 2017-01-09 11:32:03.878626462 +0000 UTC
(STDERR)Tick at 2017-01-09 11:32:03.878626462 +0000 UTC
(STDOUT)Tick at 2017-01-09 11:32:04.878601052 +0000 UTC
(STDERR)Tick at 2017-01-09 11:32:04.878601052 +0000 UTC
(STDOUT)Tick at 2017-01-09 11:32:05.878555855 +0000 UTC
(STDERR)Tick at 2017-01-09 11:32:05.878555855 +0000 UTC
(STDOUT)Tick at 2017-01-09 11:32:06.878538934 +0000 UTC
(STDERR)Tick at 2017-01-09 11:32:06.878538934 +0000 UTC
(STDOUT)Tick at 2017-01-09 11:32:07.882955526 +0000 UTC
(STDERR)Tick at 2017-01-09 11:32:07.882955526 +0000 UTC
```

# 検証

検証用スクリプトで出力されるstdout, stderrが拾えている事が確認できる。
(Log.Printlnを使っているので、時刻がつく)

```bash
vagrant@main:~/main$ go run main.go
2017/01/09 11:32:12 (STDOUT)Tick at 2017-01-09 11:32:12.03355078 +0000 UTC
2017/01/09 11:32:12 (STDERR)Tick at 2017-01-09 11:32:12.03355078 +0000 UTC
2017/01/09 11:32:13 (STDOUT)Tick at 2017-01-09 11:32:13.033572654 +0000 UTC
2017/01/09 11:32:13 (STDERR)Tick at 2017-01-09 11:32:13.033572654 +0000 UTC
2017/01/09 11:32:14 (STDOUT)Tick at 2017-01-09 11:32:14.034600479 +0000 UTC
2017/01/09 11:32:14 (STDERR)Tick at 2017-01-09 11:32:14.034600479 +0000 UTC
2017/01/09 11:32:15 (STDOUT)Tick at 2017-01-09 11:32:15.034931819 +0000 UTC
2017/01/09 11:32:15 (STDERR)Tick at 2017-01-09 11:32:15.034931819 +0000 UTC
2017/01/09 11:32:16 (STDOUT)Tick at 2017-01-09 11:32:16.034094719 +0000 UTC
2017/01/09 11:32:16 (STDERR)Tick at 2017-01-09 11:32:16.034094719 +0000 UTC
2017/01/09 11:32:17 (STDOUT)Tick at 2017-01-09 11:32:17.034009404 +0000 UTC
2017/01/09 11:32:17 (STDERR)Tick at 2017-01-09 11:32:17.034009404 +0000 UTC
2017/01/09 11:32:18 (STDOUT)Tick at 2017-01-09 11:32:18.033506672 +0000 UTC
2017/01/09 11:32:18 (STDERR)Tick at 2017-01-09 11:32:18.033506672 +0000 UTC
2017/01/09 11:32:19 (STDOUT)Tick at 2017-01-09 11:32:19.033497518 +0000 UTC
2017/01/09 11:32:19 (STDERR)Tick at 2017-01-09 11:32:19.033497518 +0000 UTC
2017/01/09 11:32:20 (STDOUT)Tick at 2017-01-09 11:32:20.033493399 +0000 UTC
2017/01/09 11:32:20 (STDERR)Tick at 2017-01-09 11:32:20.033493399 +0000 UTC
2017/01/09 11:32:21 (STDOUT)Tick at 2017-01-09 11:32:21.033498948 +0000 UTC
2017/01/09 11:32:21 (STDERR)Tick at 2017-01-09 11:32:21.033498948 +0000 UTC
```

おしまい