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

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

目錄 [−]

  1. 執行命令並獲得輸出結果
  2. 將stdout和stderr分別處理
  3. 命令執行過程中獲得輸出
  4. 命令執行過程中獲得輸出2
  5. 命令執行過程中獲得輸出3
  6. 改變執行程式的環境(environment)
  7. 預先檢查程式是否存在
  8. 管道
  9. 管道2

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

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

執行命令並獲得輸出結果

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

12345678
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分別處理。

123456789101112
func main() {cmd := exec.Command("ls", "-lah")var stdout, stderr bytes.Buffercmd.Stdout = &stdoutcmd.Stderr = &stderrerr := 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,我們還希望在控制台顯示命令執行的進度。

有點小複雜。

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {var out []bytebuf := 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 usif err == io.EOF {err = nil}return out, err}}// never reachedpanic(true)return nil, nil}func main() {cmd := exec.Command("ls", "-lah")var stdout, stderr []bytevar errStdout, errStderr errorstdoutIn, _ := 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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
// CapturingPassThroughWriter is a writer that remembers// data written to it and passes it to wtype CapturingPassThroughWriter struct {buf bytes.Bufferw io.Writer}// NewCapturingPassThroughWriter creates new CapturingPassThroughWriterfunc 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 writerfunc (w *CapturingPassThroughWriter) Bytes() []byte {return w.buf.Bytes()}func main() {var errStdout, errStderr errorcmd := 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,我們可以直接使用它。

12345678910111213141516171819202122232425262728293031323334
func main() {var stdoutBuf, stderrBuf bytes.Buffercmd := exec.Command("ls", "-lah")stdoutIn, _ := cmd.StdoutPipe()stderrIn, _ := cmd.StderrPipe()var errStdout, errStderr errorstdout := 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()格式相同。通常你不會構造一個全新的環境,而是添加自己需要的環境變數:

123456789
   cmd := exec.Command("programToExecute")additionalEnv := "FOO=bar"newEnv := append(os.Environ(), additionalEnv))cmd.Env = newEnvout, 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方法來檢查:

12345678
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有點麻煩,你可以使用下面的方法:

123456789101112131415161718192021222324252627
package mainimport (    "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`。

12345678910111213141516
package mainimport (    "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"來實現。

12345678910111213141516
package mainimport ("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))}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.