Go基础篇【第5篇】: 内置库模块 exec

Package exec runs external commands. It wraps os.StartProcess to make it easier to remap stdin and stdout, connect I/O with pipes, and do other adjustments.

Unlike the "system" library call from C and other languages, the os/exec package intentionally does not invoke the system shell and does not expand any glob patterns or handle other expansions, pipelines, or redirections typically done by shells. The package behaves more like C's "exec" family of functions. To expand glob patterns, either call the shell directly, taking care to escape any dangerous input, or use the path/filepath package's Glob function. To expand environment variables, use package os's ExpandEnv.

Note that the examples in this package assume a Unix system. They may not run on Windows, and they do not run in the Go Playground used by golang.org and godoc.org.

LookPath

func LookPath(file string) (string, error)

在环境变量PATH指定的目录中搜索可执行文件,如file中有斜杠,则只在当前目录搜索。

即:默认在系统的环境变量里查找给定的可执行命令文件,如果查找到返回路径,否则报错,unix是$PATH,windows是$PATH$。可提供相对路径下进行查找,并返回相对路径



type Cmd struct {
    // Path是将要执行的命令的路径。
    //
    // 该字段不能为空,如为相对路径会相对于Dir字段。
    Path string
    // Args保管命令的参数,包括命令名作为第一个参数;如果为空切片或者nil,相当于无参数命令。
    //
    // 典型用法下,Path和Args都应被Command函数设定。
    Args []string
    // Env指定进程的环境,如为nil,则是在当前进程的环境下执行。
    Env []string
    // Dir指定命令的工作目录。如为空字符串,会在调用者的进程当前目录下执行。
    Dir string
    // Stdin指定进程的标准输入,如为nil,进程会从空设备读取(os.DevNull)
    Stdin io.Reader
    // Stdout和Stderr指定进程的标准输出和标准错误输出。
    //
    // 如果任一个为nil,Run方法会将对应的文件描述符关联到空设备(os.DevNull)
    //
    // 如果两个字段相同,同一时间最多有一个线程可以写入。
    Stdout io.Writer
    Stderr io.Writer
    // ExtraFiles指定额外被新进程继承的已打开文件流,不包括标准输入、标准输出、标准错误输出。
    // 如果本字段非nil,entry i会变成文件描述符3+i。
    //
    // BUG: 在OS X 10.6系统中,子进程可能会继承不期望的文件描述符。
    // http://golang.org/issue/2603
    ExtraFiles []*os.File
    // SysProcAttr保管可选的、各操作系统特定的sys执行属性。
    // Run方法会将它作为os.ProcAttr的Sys字段传递给os.StartProcess函数。
    SysProcAttr *syscall.SysProcAttr
    // Process是底层的,只执行一次的进程。
    Process *os.Process
    // ProcessState包含一个已经存在的进程的信息,只有在调用Wait或Run后才可用。
    ProcessState *os.ProcessState
    // 内含隐藏或非导出字段
}

Cmd代表一个正在准备或者在执行中的外部命令。

注:exec在执行调用系统命令时,会先对需要执行的操作进行一次封装,然后在执行。封装后的命令对象具有以上struct属性。而封装方式即使用下边的command函数。

Command

func Command(name string, arg ...string) *Cmd

函数返回一个*Cmd,用于使用给出的参数执行name指定的程序。返回值只设定了Path和Args两个参数。

如果name不含路径分隔符(如果不是相对路径),将使用LookPath获取完整路径(就是用默认的全局变量路径);否则直接使用name。参数arg不应包含命令名。

cmd := exec.Command("go","version")
fmt.Println(cmd.Args, cmd.Path)

注:在调用命令执行封装时,如果不提供相对路径,系统会使用LookPath获取完整路径;即这里可以给一个相对路径。

以上操作只会将命令进行封装,相当于告诉系统将进行哪些操作,但是执行时无法获取相关信息,因此我们还需要连接到命令执行时相关的输入输出pipe。################################################################################################

我们可以通过指定一个对象连接到对应的管道进行传输参数(stdinpipe),获取输出(stdoutpipe),获取错误(stderrpipe)

StdinPipe

func (c *Cmd) StdinPipe() (io.WriteCloser, error)  //err 返回的是执行函数时的错误
package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    cmd := exec.Command("cat")
    stdin, err := cmd.StdinPipe()       //指定stdin连接StdinPipe,然后操作stdin就实现了对command的参数传递
    if err != nil {
        fmt.Println(err)
    }
    _, err = stdin.Write([]byte("tmp.txt"))   //字节切片
    if err != nil {
        fmt.Println(err)
    }
    stdin.Close()
    cmd.Stdout = os.Stdout     //终端标准输出tmp.txt
    cmd.Start()
}

StdinPipe方法返回一个在命令Start后与命令标准输入关联的管道。Wait方法获知命令结束后会关闭这个管道。必要时调用者可以调用Close方法来强行关闭管道,例如命令在输入关闭后才会执行返回时需要显式关闭管道。

StdoutPipe

func (c *Cmd) StdoutPipe() (io.ReadCloser, error)    //err 返回的是执行函数时的错误
func main() {
    cmd := exec.Command("ls")
    stdout, err := cmd.StdoutPipe()  //指向cmd命令的stdout,然后就可以从stdout读出信息
    cmd.Start()
    content, err := ioutil.ReadAll(stdout)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(content))     //输出ls命令查看到的内容
}

StdoutPipe方法返回一个在命令Start后与命令标准输出关联的管道。Wait方法获知命令结束后会关闭这个管道,一般不需要显式的关闭该管道。但是在从管道读取完全部数据之前调用Wait是错误的;同样使用StdoutPipe方法时调用Run函数也是错误的。

StderrPipe

func (c *Cmd) StderrPipe() (io.ReadCloser, error)   //err 返回的是执行函数时的错误
import (
"fmt"
"io/ioutil"
"os/exec"
)

func main() {
    c := exec.Command("mv", "hello")
    i, err := c.StderrPipe()
    if err != nil {
        fmt.Printf("Error: %s\n", err)
        return
    }
    if err = c.Start(); err != nil {    //表示命令正确执行了
        fmt.Printf("Error: %s\n", err)
    }
    b, _ := ioutil.ReadAll(i)        //读取命令执行的返回结果(命令执行结果的错误信息)
    if err := c.Wait(); err != nil {
        fmt.Printf("Error: %s\n", err)   //Error: exit status 1 mv: missing file argument Try `mv --help' for more information.
    }                                                                 
    fmt.Println(string(b))
}

StderrPipe方法返回一个在命令Start后与命令标准错误输出关联的管道。Wait方法获知命令结束后会关闭这个管道,一般不需要显式的关闭该管道。但是在从管道读取完全部数据之前调用Wait是错误的;同样使用StderrPipe方法时调用Run函数也是错误的

#################################################################



func (c *Cmd) Run() error

Run执行c包含的命令,并阻塞直到完成。

如果命令成功执行,stdin、stdout、stderr的转交没有问题,并且返回状态码为0,方法的返回值为nil【执行Run函数的返回状态,正确执行Run函数,并不代表正确执行了命令】;如果函数没有执行或者执行失败,会返回*ExitError类型的错误;否则返回的error可能是表示I/O问题。

即:该命令只会执行且阻塞到执行结束,如果执行函数有错则返回报错信息,没错则返回nil,并不会返回执行结果。

Start

func (c *Cmd) Start() error

Start开始执行c包含的命令,但并不会等待该命令完成即返回。Wait方法会返回命令的返回状态码并在命令返回后释放相关的资源。

Example

Wait

func (c *Cmd) Wait() error

Wait会阻塞直到该命令执行完成,该命令必须是被Start方法开始执行的。

如果命令成功执行,stdin、stdout、stderr的转交没有问题,并且返回状态码为0,方法的返回值为nil;如果命令没有执行或者执行失败,会返回*ExitError类型的错误;否则返回的error可能是表示I/O问题。Wait方法会在命令返回后释放相关的资源。

Output

func (c *Cmd) Output() ([]byte, error)

执行命令并返回标准输出的切片。不用通过pipe方式获取命令的执行结果

import (
"fmt"
"os/exec"
)

func main() {
    c, err := exec.Command("date").Output()
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(c)) //    Sat Jan 4 17:07:36 2014 这个是标准库里的例子
}

CombinedOutput

func (c *Cmd) CombinedOutput() ([]byte, error)

执行命令并返回标准输出和错误输出合并的切片。