[golang][譯]使用os/exec執行命令


[golang][譯]使用os/exec執行命令

https://colobu.com/2017/06/19/advanced-command-execution-in-Go-with-os-exec/

原文: Advanced command execution in Go with os/exec by Krzysztof Kowalczyk.
完整代碼在作者的github上: advanced-exec

Go可以非常方便地執行外部程序,讓我們開始探索之旅吧。

執行命令並獲得輸出結果

最簡單的例子就是運行ls -lah並獲得組合在一起的stdout/stderr輸出。

func main() {
    cmd := exec.Command("ls", "-lah")
    out, err := cmd.CombinedOutput()
    if err != nil {
        log.Fatalf("cmd.Run() failed with %s\n", err)
    }
    fmt.Printf("combined out:\n%s\n", string(out))
}

將stdout和stderr分別處理

和上面的例子類似,只不過將stdout和stderr分別處理。

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\n", err)
    }
    outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
    fmt.Printf("out:\n%s\nerr:\n%s\n", outStr, errStr)
}

命令執行過程中獲得輸出

如果一個命令需要花費很長時間才能執行完呢?

除了能獲得它的stdout/stderr,我們還希望在控制台顯示命令執行的進度。

有點小復雜。

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\n", err)
    }
    if errStdout != nil || errStderr != nil {
        log.Fatalf("failed to capture stdout or stderr\n")
    }
    outStr, errStr := string(stdout), string(stderr)
    fmt.Printf("\nout:\n%s\nerr:\n%s\n", outStr, errStr)
}

命令執行過程中獲得輸出2

上一個方案雖然工作,但是看起來copyAndCapture好像重新實現了io.Copy。由於Go的接口的功能,我們可以重用io.Copy

我們寫一個CapturingPassThroughWriterstruct,它實現了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'\n", 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\n", err)
    }
    if errStdout != nil || errStderr != nil {
        log.Fatalf("failed to capture stdout or stderr\n")
    }
    outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
    fmt.Printf("\nout:\n%s\nerr:\n%s\n", 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'\n", 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\n", err)
    }
    if errStdout != nil || errStderr != nil {
        log.Fatal("failed to capture stdout or stderr\n")
    }
    outStr, errStr := string(stdoutBuf.Bytes()), string(stderrBuf.Bytes())
    fmt.Printf("\nout:\n%s\nerr:\n%s\n", outStr, errStr)
}

改變執行程序的環境(environment)

你已經知道了怎么在程序中獲得環境變量,對吧: `os.Environ()`返回所有的環境變量[]string,每個字符串以FOO=bar格式存在。FOO是環境變量的名稱,bar是環境變量的值, 也就是os.Getenv("FOO")的返回值。

有時候你可能想修改執行程序的環境。

你可設置exec.CmdEnv的值,和os.Environ()格式相同。通常你不會構造一個全新的環境,而是添加自己需要的環境變量:

 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\n", err)
}
fmt.Printf("%s", out)

包 shurcooL/go/osutil提供了便利的方法設置環境變量。

預先檢查程序是否存在

想象一下你寫了一個程序需要花費很長時間執行,再最后你調用foo做一些基本的任務。

如果foo程序不存在,程序會執行失敗。

當然如果我們預先能檢查程序是否存在就完美了,如果不存在就打印錯誤信息。

你可以調用exec.LookPath方法來檢查:

func checkLsExists() {
    path, err := exec.LookPath("ls")
    if err != nil {
        fmt.Printf("didn't find 'ls' executable\n")
    } else {
        fmt.Printf("'ls' executable is in '%s'\n", path)
    }
}

另一個檢查的辦法就是讓程序執行一個空操作, 比如傳遞參數"--help"顯示幫助信息。

下面的章節是譯者補充的內容

管道

我們可以使用管道將多個命令串聯起來, 上一個命令的輸出是下一個命令的輸入。

使用os.Exec有點麻煩,你可以使用下面的方法:

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

或者直接使用CmdStdoutPipe方法,而不是自己創建一個io.Pipe`。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM