[译]使用os/exec执行命令
smallnest · · 1520 次点击 · · 开始浏览目录 [−]
原文: 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.Cmd的Env的值,和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 = wc2.Stdin = rvar b2 bytes.Bufferc2.Stdout = &b2c1.Start()c2.Start()c1.Wait()w.Close()c2.Wait()io.Copy(os.Stdout, &b2)}
或者直接使用Cmd的StdoutPipe方法,而不是自己创建一个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))}
有疑问加站长微信联系(非本文作者)
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传
收入到我管理的专栏 新建专栏
目录 [−]
原文: 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.Cmd的Env的值,和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 = wc2.Stdin = rvar b2 bytes.Bufferc2.Stdout = &b2c1.Start()c2.Start()c1.Wait()w.Close()c2.Wait()io.Copy(os.Stdout, &b2)}
或者直接使用Cmd的StdoutPipe方法,而不是自己创建一个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))}