执行命令并获得输出结果
最简单的例子就是运行ls -lah并获得组合在一起的stdout/stderr输出。
package main import ( "fmt" "log" "os/exec" ) func main() { cmd := exec.Command("ls", "-lah") out, err := cmd.CombinedOutput() if err != nil { log.Printf("cmd.Run() failed with %s\n", err) } fmt.Printf("combined out:\n%s\n", string(out)) }
将stdout和stderr分别处理
和上面的例子类似,只不过将stdout和stderr分别处理。
package main import ( "bytes" "fmt" "log" "os/exec" ) 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.Printf("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,我们还希望在控制台显示命令执行的进度。
有点小复杂。
package main import ( "fmt" "io" "log" "os" "os/exec" ) 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...) w.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 } } } func main() { cmd := exec.Command("/bin/bash", "test.sh") 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.Printf("cmd.Run() failed with %s\n", err) } if errStdout != nil || errStderr != nil { log.Printf("failed to capture stdout or stderr\n") } outStr, errStr := string(stdout), string(stderr) fmt.Printf("\nout:\n%s\nerr:\n%s\n", outStr, errStr) } 下面是test.sh文件内容 while [[ $i -le 10 ]]; do let i=i+1 echo $i abcdefghigklmnopqrstovwxyz sleep 1 done 以上程序达到的效果,模拟有些shell执行时间过长,只有执行完成后才知道执行结果 上述程序可以将shell执行的标准输出实时打印出来 [root@k8s-test-2 /home/andy/go-test]#go run main.go 1 abcdefghigklmnopqrstovwxyz 2 abcdefghigklmnopqrstovwxyz 3 abcdefghigklmnopqrstovwxyz 4 abcdefghigklmnopqrstovwxyz 5 abcdefghigklmnopqrstovwxyz 6 abcdefghigklmnopqrstovwxyz 7 abcdefghigklmnopqrstovwxyz 8 abcdefghigklmnopqrstovwxyz 9 abcdefghigklmnopqrstovwxyz 10 abcdefghigklmnopqrstovwxyz 11 abcdefghigklmnopqrstovwxyz out: 1 abcdefghigklmnopqrstovwxyz 2 abcdefghigklmnopqrstovwxyz 3 abcdefghigklmnopqrstovwxyz 4 abcdefghigklmnopqrstovwxyz 5 abcdefghigklmnopqrstovwxyz 6 abcdefghigklmnopqrstovwxyz 7 abcdefghigklmnopqrstovwxyz 8 abcdefghigklmnopqrstovwxyz 9 abcdefghigklmnopqrstovwxyz 10 abcdefghigklmnopqrstovwxyz 11 abcdefghigklmnopqrstovwxyz err:
命令执行过程中获得输出2
上一个方案虽然工作,但是看起来copyAndCapture好像重新实现了io.Copy。由于Go的接口的功能,我们可以重用io.Copy。
我们写一个CapturingPassThroughWriterstruct,它实现了io.Writer接口。它会捕获所有的数据并写入到底层的io.Writer。
package main import ( "bytes" "fmt" "io" "log" "os" "os/exec" ) // 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.Printf("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.Printf("cmd.Run() failed with %s\n", err) } if errStdout != nil || errStderr != nil { log.Printf("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,我们可以直接使用它。
package main import ( "bytes" "fmt" "io" "log" "os" "os/exec" ) 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.Printf("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.Printf("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()格式相同。通常你不会构造一个全新的环境,而是添加自己需要的环境变量:
package main import ( "fmt" "log" "os" "os/exec" ) func main() { cmd := exec.Command("programToExecute") additionalEnv := "FOO=bar" newEnv := append(os.Environ(), additionalEnv)) cmd.Env = newEnv out, err := cmd.CombinedOutput() if err != nil { log.Printf("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) }
或者直接使用Cmd的StdoutPipe方法,而不是自己创建一个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://github.com/kjk/go-cookbook/tree/master/advanced-exec
–
–
评论前必须登录!
注册