[golang][訳]os/execを使ってコマンドを実行します.

22349 ワード

[golang][訳]os/execを使ってコマンドを実行します.
https://colobu.com/2017/06/19/advanced-command-execution-in-Go-with-os-exec/
原文: A dvd command execution in Go with os/exec by Krzysztof Kwalczyk.完全コードは著者のgithubにあります. advance d-exec
Goは非常に便利に外部プログラムを実行できます.探索の旅を始めましょう.
コマンドを実行して出力結果を取得します.
最も簡単な例は、ls -lahを実行し、組み合わせられたstdout/stder出力を得ることである.
func main() {
    cmd := exec.Command("ls", "-lah")
    out, err := cmd.CombinedOutput()
    if err != nil {
        log.Fatalf("cmd.Run() failed with %s
", err) } fmt.Printf("combined out:
%s
", string(out)) }
stdoutとstderをそれぞれ処理します.
上の例と似ていますが、stdoutとstderは別々に処理します.
func main() {
    cmd := exec.Command("ls", "-lah")
    var stdout, stderr bytes.Buffer
    cmd.Stdout = &stdout
    cmd.Stderr = &stderr
    err := cmd.Run()
    if err != nil {
        log.Fatalf("cmd.Run() failed with %s
", err) } outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes()) fmt.Printf("out:
%s
err:
%s
", outStr, errStr) }
コマンド実行中に出力を取得します.
もし一つの命令が実行されるまでに時間がかかりますか?
そのstdout/stderが得られるほか、コマンド実行の進捗をコンソールに表示したいです.
ちょっと複雑です
func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {
    var out []byte
    buf := make([]byte, 1024, 1024)
    for {
        n, err := r.Read(buf[:])
        if n > 0 {
            d := buf[:n]
            out = append(out, d...)
            os.Stdout.Write(d)
        }
        if err != nil {
            // Read returns io.EOF at the end of file, which is not an error for us
            if err == io.EOF {
                err = nil
            }
            return out, err
        }
    }
    // never reached
    panic(true)
    return nil, nil
}
func main() {
    cmd := exec.Command("ls", "-lah")
    var stdout, stderr []byte
    var errStdout, errStderr error
    stdoutIn, _ := cmd.StdoutPipe()
    stderrIn, _ := cmd.StderrPipe()
    cmd.Start()
    go func() {
        stdout, errStdout = copyAndCapture(os.Stdout, stdoutIn)
    }()
    go func() {
        stderr, errStderr = copyAndCapture(os.Stderr, stderrIn)
    }()
    err := cmd.Wait()
    if err != nil {
        log.Fatalf("cmd.Run() failed with %s
", err) } if errStdout != nil || errStderr != nil { log.Fatalf("failed to capture stdout or stderr
") } outStr, errStr := string(stdout), string(stderr) fmt.Printf("
out:
%s
err:
%s
", outStr, errStr) }
コマンド実行中に出力2を取得します.
前の案は動作していますが、copyAndCaptureio.Copyを再実現したように見えます.Goのインターフェースの機能のために、io.Copyを再利用することができます.CapturingPassThroughWriterのstructを書いて、io.Writerインターフェースを実現しました.すべてのデータを捕獲し、最終層のio.Writerに書き込む.
// CapturingPassThroughWriter is a writer that remembers
// data written to it and passes it to w
type CapturingPassThroughWriter struct {
    buf bytes.Buffer
    w io.Writer
}
// NewCapturingPassThroughWriter creates new CapturingPassThroughWriter
func NewCapturingPassThroughWriter(w io.Writer) *CapturingPassThroughWriter {
    return &CapturingPassThroughWriter{
        w: w,
    }
}
func (w *CapturingPassThroughWriter) Write(d []byte) (int, error) {
    w.buf.Write(d)
    return w.w.Write(d)
}
// Bytes returns bytes written to the writer
func (w *CapturingPassThroughWriter) Bytes() []byte {
    return w.buf.Bytes()
}
func main() {
    var errStdout, errStderr error
    cmd := exec.Command("ls", "-lah")
    stdoutIn, _ := cmd.StdoutPipe()
    stderrIn, _ := cmd.StderrPipe()
    stdout := NewCapturingPassThroughWriter(os.Stdout)
    stderr := NewCapturingPassThroughWriter(os.Stderr)
    err := cmd.Start()
    if err != nil {
        log.Fatalf("cmd.Start() failed with '%s'
", err) } go func() { _, errStdout = io.Copy(stdout, stdoutIn) }() go func() { _, errStderr = io.Copy(stderr, stderrIn) }() err = cmd.Wait() if err != nil { log.Fatalf("cmd.Run() failed with %s
", err) } if errStdout != nil || errStderr != nil { log.Fatalf("failed to capture stdout or stderr
") } outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes()) fmt.Printf("
out:
%s
err:
%s
", outStr, errStr) }
コマンド実行中に出力3を取得します.
実際にGo標準ライブラリは、より一般的なio.MultiWriterを含んでいます.私たちは直接にそれを使うことができます.
func main() {
    var stdoutBuf, stderrBuf bytes.Buffer
    cmd := exec.Command("ls", "-lah")
    stdoutIn, _ := cmd.StdoutPipe()
    stderrIn, _ := cmd.StderrPipe()
    var errStdout, errStderr error
    stdout := io.MultiWriter(os.Stdout, &stdoutBuf)
    stderr := io.MultiWriter(os.Stderr, &stderrBuf)
    err := cmd.Start()
    if err != nil {
        log.Fatalf("cmd.Start() failed with '%s'
", err) } go func() { _, errStdout = io.Copy(stdout, stdoutIn) }() go func() { _, errStderr = io.Copy(stderr, stderrIn) }() err = cmd.Wait() if err != nil { log.Fatalf("cmd.Run() failed with %s
", err) } if errStdout != nil || errStderr != nil { log.Fatal("failed to capture stdout or stderr
") } outStr, errStr := string(stdoutBuf.Bytes()), string(stderrBuf.Bytes()) fmt.Printf("
out:
%s
err:
%s
", outStr, errStr) }
プログラムを実行する環境を変更します.
プログラムで環境変数を取得する方法は分かりましたよね.[]stringは環境変数の名前であり、FOO=barは環境変数の値であり、FOOの戻り値である.
プログラムを実行する環境を変更したい場合があります.baros.Getenv("FOO")の値を設定してもいいです.exec.Cmdのフォーマットと同じです.通常は新しい環境を作るのではなく、自分に必要な環境変数を追加します.
 cmd := exec.Command("programToExecute")
additionalEnv := "FOO=bar"
newEnv := append(os.Environ(), additionalEnv))
cmd.Env = newEnv
out, err := cmd.CombinedOutput()
if err != nil {
    log.Fatalf("cmd.Run() failed with %s
", err) } fmt.Printf("%s", out)
包みをつくる shurcooL/go/Outilは、環境変数の設定に便利な方法を提供します.
あらかじめプログラムが存在するかどうかをチェックします.
想像してみてください.プログラムを書くには時間がかかります.最後にEnvを呼び出して基本的な仕事をしてください.os.Environ()プログラムが存在しない場合、プログラムは実行に失敗します.
もちろん、プログラムが存在するかどうかを確認しておけば完璧です.存在しないなら、エラー情報を印刷します.
チェックするためにfoo方法を呼び出すことができます.
func checkLsExists() {
    path, err := exec.LookPath("ls")
    if err != nil {
        fmt.Printf("didn't find 'ls' executable
") } else { fmt.Printf("'ls' executable is in '%s'
", path) } }
もう一つの検査の方法は、プログラムに空の操作を実行させることです.例えば、パラメータを渡す「--help」にヘルプ情報を表示させることです.
次の章は翻訳者が追加した内容です.
パイプ
パイプラインを使って複数のコマンドを直列に接続できます.前のコマンドの出力は次のコマンドの入力です.fooを使うのはちょっと面倒くさいです.次の方法を使ってもいいです.
package main
import (
    "bytes"
    "io"
    "os"
    "os/exec"
)
func main() {
    c1 := exec.Command("ls")
    c2 := exec.Command("wc", "-l")
    r, w := io.Pipe() 
    c1.Stdout = w
    c2.Stdin = r
    var b2 bytes.Buffer
    c2.Stdout = &b2
    c1.Start()
    c2.Start()
    c1.Wait()
    w.Close()
    c2.Wait()
    io.Copy(os.Stdout, &b2)
}
または、自分でio.Pipeを作成するのではなく、exec.LookPathos.Execの方法を直接使用する`.
package main
import (
    "os"
    "os/exec"
)
func main() {
    c1 := exec.Command("ls")
    c2 := exec.Command("wc", "-l")
    c2.Stdin, _ = c1.StdoutPipe()
    c2.Stdout = os.Stdout
    _ = c2.Start()
    _ = c1.Run()
    _ = c2.Wait()
}
パイプ2
上のソリューションはGoスタイルのソリューションです.実際には「Trick」を使ってもいいです.
package main
import (
    "fmt"
    "os/exec"
)
func main() {
    cmd := "cat /proc/cpuinfo | egrep '^model name' | uniq | awk '{print substr($0, index($0,$4))}'"
    out, err := exec.Command("bash", "-c", cmd).Output()
    if err != nil {
        fmt.Printf("Failed to execute command: %s", cmd)
    }
    fmt.Println(string(out))
}
 https://www.cnblogs.com/landv/p/11584253.html