zoukankan      html  css  js  c++  java
  • Go exec 执行命令

    Go Exec执行命令

    执行命令并获得输出结果

    最简单的例子就是运行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
    ", err)
    	}
    	fmt.Printf("combined out:
    %s
    ", 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
    ", err)
    	}
    	outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
    	fmt.Printf("out:
    %s
    err:
    %s
    ", 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
    ", err)
    	}
    	if errStdout != nil || errStderr != nil {
    		log.Fatalf("failed to capture stdout or stderr
    ")
    	}
    	outStr, errStr := string(stdout), string(stderr)
    	fmt.Printf("
    out:
    %s
    err:
    %s
    ", 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'
    ", 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
    ", err)
    	}
    	if errStdout != nil || errStderr != nil {
    		log.Fatalf("failed to capture stdout or stderr
    ")
    	}
    	outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
    	fmt.Printf("
    out:
    %s
    err:
    %s
    ", 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'
    ", 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
    ", err)
    	}
    	if errStdout != nil || errStderr != nil {
    		log.Fatal("failed to capture stdout or stderr
    ")
    	}
    	outStr, errStr := string(stdoutBuf.Bytes()), string(stderrBuf.Bytes())
    	fmt.Printf("
    out:
    %s
    err:
    %s
    ", 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
    ", 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
    ")
    	} else {
    		fmt.Printf("'ls' executable is in '%s'
    ", 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))
    }
    
    Songzhibin
  • 相关阅读:
    FreeCommander 学习手册
    String详解, String和CharSequence区别, StringBuilder和StringBuffer的区别 (String系列之1)
    StringBuffer 详解 (String系列之3)
    StringBuilder 详解 (String系列之2)
    java io系列26之 RandomAccessFile
    java io系列25之 PrintWriter (字符打印输出流)
    java io系列24之 BufferedWriter(字符缓冲输出流)
    java io系列23之 BufferedReader(字符缓冲输入流)
    java io系列22之 FileReader和FileWriter
    java io系列21之 InputStreamReader和OutputStreamWriter
  • 原文地址:https://www.cnblogs.com/binHome/p/14149834.html
Copyright © 2011-2022 走看看