LoginSignup
10
10

More than 5 years have passed since last update.

プログラム内で起動した外部プロセスをチョメチョメする

Last updated at Posted at 2014-07-30

mattnさんにシンプルなコードを教えて頂いたので、コメント欄をご欄ください

先日、とあるイベントに参加したときにGo言語から外部プロセスを起動し、起動したプロセスを操作する方法が分からず無駄な時間を過ごしてしまいました。
次回も同じ轍を踏まないようにコマンドを起動してチョメチョメするライブラリを作成しました。

参考サイト

ライブラリの作成にあたり、PythonMatrixJpさんのプログラムを参考に作成しました。

どんなライブラリなのか?

外部プロセスを操作するライブラリ exec.Cmd を拡張したライブラリです。名前をpfprocといいます。

  1. exec.Commandと同じ引数でインスタを作成します。
  2. Startメソッドで外部プロセスを起動します。
  3. 外部プロセスから標準(エラー)出力をターミナルに表示します。
  4. Inputメソッドを使って外部プロセスにデータを送ります。(標準入力)
  5. 処理が完了するまで待つには WaitProcメソッドを使います。

pfprocの利用サンプル

package main

import (
    "fmt"
    "os"
    "pfproc"
    "strings"
)

func exitOnErr(msg string, err error) {
    if err != nil {
        fmt.Println(msg, err)
        os.Exit(1)
    }
}

func main() {
    proc, err := pfproc.NewPfProc("bash")
    exitOnErr("create process", err)
    proc.StdoutFunc = func(text string) {
        // ファイル名がmain.goのときは hehehe に置き換える
        if strings.Index(text, "main.go") != -1 {
            text = "hehehe"
        }
        fmt.Println(text)
    }
    proc.Start()
    proc.Input("pwd")
    proc.Input("ls")
    proc.Input("exit")
    proc.WaitProc()
}

pfprocのソースコード

package pfproc

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

type PfProc struct {
    *exec.Cmd
    stdin      chan string
    stdout     chan string
    stderr     chan string
    quit       chan error
    StdoutFunc func(text string)
    StderrFunc func(text string)
}

func NewPfProc(name string, args ...string) (*PfProc, error) {
    self := &PfProc{
        exec.Command(name, args...),
        make(chan string),
        make(chan string),
        make(chan string),
        make(chan error),
        func(text string) { fmt.Println(text) },
        func(text string) { fmt.Println(text) },
    }

    in, err := self.StdinPipe()
    if err != nil {
        return nil, err
    }
    go inprocess(in, self.stdin)

    out, _ := self.StdoutPipe()
    if err != nil {
        return nil, err
    }
    go outprocess(out, self.stdout)

    ste, _ := self.StderrPipe()
    if err != nil {
        return nil, err
    }
    go outprocess(ste, self.stderr)

    return self, nil
}

func (self *PfProc) Input(text string) {
    self.stdin <- text
}

func (self *PfProc) WaitProc() {

    go func() {
        self.quit <- self.Wait()
        close(self.stdin)
        close(self.stdout)
        close(self.stderr)
    }()

    for {
        select {
        case line, ok := <-self.stdout:
            if ok {
                self.StdoutFunc(line)
            }
        case line, ok := <-self.stderr:
            if ok {
                self.StderrFunc(line)
            }
        case err := <-self.quit:
            if err != nil {
                fmt.Println(err)
            }
            return
        }
    }
}

func inprocess(in io.WriteCloser, std chan string) {
    for {
        line, ok := <-std
        if ok {
            in.Write([]byte(line + "\n"))
        }
    }
}

func outprocess(out io.ReadCloser, std chan string) {
    reader := bufio.NewReader(out)
    for {
        line, _, err := reader.ReadLine()
        if err != nil {
            return
        }
        std <- string(line)
    }
}
10
10
2

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
10
10