いろいろ備忘録日記

主に .NET とか Go とか Python絡みのメモを公開しています。

Goメモ-142 (exec.Command で パイプストリーム を使ってコマンド実行)

概要

よく忘れるのでメモメモ。

少し前に exec.Command について以下をメモしていました。

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

(*Cmd)には、StdinPipe, StdoutPipe, StderrPipeというメソッドがあり、そこから標準入力、標準出力、標準エラー出力へのパイプストリームが取得できます。

データを一発ではなく、順に流していき、結果を取得するというような使い方ができます。

注意点としては、取得したパイプはそれぞれ非同期で扱わないといけないという点と、コマンドの実行に Run ではなく、StartとWaitを利用するという点ですね。

サンプル

package cmdexec

import (
    "bufio"
    "errors"
    "fmt"
    "io"
    "os/exec"
    "runtime"
)

// Pipe は、 (*Cmd).StdinPipe,StdoutPipe,StderrPipeのサンプルです。
//
// REFERENCES:
//   - https://golang.org/pkg/os/exec/#example_Cmd_StdinPipe
func Pipe() error {
    if runtime.GOOS == "windows" {
        return errors.New("this example cannot run on Windows, sorry")
    }

    const (
        Shell = "/bin/bash"
    )

    var (
        cmd     *exec.Cmd      // コマンド
        fd0Pipe io.WriteCloser // 標準入力のパイプ
        fd1Pipe io.ReadCloser  // 標準出力のパイプ
        fd2Pipe io.ReadCloser  // 標準エラー出力のパイプ
    )

    // コマンド構築
    cmd = exec.Command(Shell, "-c", "tr a-z A-Z | sort; echo ...done... 1>&2")

    //
    // パイプを取得
    //   パイプを扱う場合は、それぞれを非同期で処理する必要がある。
    //
    fd0Pipe, _ = cmd.StdinPipe()
    fd1Pipe, _ = cmd.StdoutPipe()
    fd2Pipe, _ = cmd.StderrPipe()

    //
    // コマンド実行
    //   StdoutPipe または StderrPipe を利用する場合
    //   (*Cmd).Run() でコマンドを実行しない。
    //   (*Cmd).Start() で実行して (*Cmd).Wait() で待つようにする。
    //
    if err := cmd.Start(); err != nil {
        return err
    }

    // 標準入力のハンドリング
    go func() {
        //
        // 入力が完了したことを示すために明示的に Close する
        //
        defer fd0Pipe.Close()

        io.WriteString(fd0Pipe, "python\n")
        io.WriteString(fd0Pipe, "csharp\n")
        io.WriteString(fd0Pipe, "golang\n")
        io.WriteString(fd0Pipe, "java\n")
    }()

    // 標準出力のハンドリング
    go func() {
        //
        // 標準出力のパイプは (*Cmd).Wait() の呼び出しにて Close されるので
        // 通常呼ぶ必要はない。
        //
        scanner := bufio.NewScanner(fd1Pipe)
        for scanner.Scan() {
            fmt.Println(scanner.Text())
        }
    }()

    // 標準エラー出力のハンドリング
    go func() {
        //
        // 標準エラー出力のパイプは (*Cmd).Wait() の呼び出しにて Close されるので
        // 通常呼ぶ必要はない。
        //
        b, _ := io.ReadAll(fd2Pipe)
        fmt.Println(string(b))
    }()

    // コマンド終了待ち
    if err := cmd.Wait(); err != nil {
        return err
    }

    return nil
}

try-golang/pipe.go at master · devlights/try-golang · GitHub

実行すると以下のようになります。

gitpod /workspace/try-golang $ make run

go get -d ./...
go run -race github.com/devlights/try-golang/cmd/trygolang -onetime -example ""

ENTER EXAMPLE NAME: cmdexec_pipe
[Name] "cmdexec_pipe"

CSHARP
GOLANG
JAVA
PYTHON
...done...



[Elapsed] 8.048522ms

標準出力に渡したデータがちゃんと処理されて、ソートされて出力されていますね。

参考資料

golang.org


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com