【Golang】使用 - 执行shell命令

Posted by 西维蜀黍 on 2021-04-05, Last Modified on 2021-09-21

执行命令并获得输出结果

最简单的例子就是运行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)
}	

管道

我们可以使用管道将多个命令串联起来, 上一个命令的输出是下一个命令的输入。

使用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()
}

Reference