LoginSignup
13
16

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-01-09

なにこれ

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

仕組み

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

プログラム


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に時刻を投げるものである。
これを使って、上記スクリプトがに標準出力・エラー出力を拾えているか確認する。

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)
}

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


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を使っているので、時刻がつく)

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

おしまい

13
16
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
13
16