• 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
  • 相关阅读:
    点分治 (等级排) codeforces 321C
    树上点分治 poj 1741
    判断点在直线的左侧还是右侧
    树的重心
    链式前向星
    树上点的分治
    构造 素数
    二进制 + 模拟
    枚举 + 三分 (游标)
    枚举 + 三分
  • 原文地址:https://www.cnblogs.com/binHome/p/14149834.html
Copyright © 2020-2023  润新知