在golang里调用外部命令

最近正在用go写一个各语言的lint server,需要调用外部的命令,所以这里整理一下用到的方法。

Command

os/exec包中有一个 type 叫 cmd,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
type Cmd struct {
// Path is the path of the command to run.
//
// This is the only field that must be set to a non-zero
// value. If Path is relative, it is evaluated relative
// to Dir.
Path string

// Args holds command line arguments, including the command as Args[0].
// If the Args field is empty or nil, Run uses {Path}.
//
// In typical use, both Path and Args are set by calling Command.
Args []string

// Env specifies the environment of the process.
// If Env is nil, Run uses the current process's environment.
Env []string

// Dir specifies the working directory of the command.
// If Dir is the empty string, Run runs the command in the
// calling process's current directory.
Dir string

// Stdin specifies the process's standard input.
// If Stdin is nil, the process reads from the null device (os.DevNull).
// If Stdin is an *os.File, the process's standard input is connected
// directly to that file.
// Otherwise, during the execution of the command a separate
// goroutine reads from Stdin and delivers that data to the command
// over a pipe. In this case, Wait does not complete until the goroutine
// stops copying, either because it has reached the end of Stdin
// (EOF or a read error) or because writing to the pipe returned an error.
Stdin io.Reader

// Stdout and Stderr specify the process's standard output and error.
//
// If either is nil, Run connects the corresponding file descriptor
// to the null device (os.DevNull).
//
// If Stdout and Stderr are the same writer, at most one
// goroutine at a time will call Write.
Stdout io.Writer
Stderr io.Writer

// ExtraFiles specifies additional open files to be inherited by the
// new process. It does not include standard input, standard output, or
// standard error. If non-nil, entry i becomes file descriptor 3+i.
//
// BUG(rsc): On OS X 10.6, child processes may sometimes inherit unwanted fds.
// https://golang.org/issue/2603
ExtraFiles []*os.File

// SysProcAttr holds optional, operating system-specific attributes.
// Run passes it to os.StartProcess as the os.ProcAttr's Sys field.
SysProcAttr *syscall.SysProcAttr

// Process is the underlying process, once started.
Process *os.Process

// ProcessState contains information about an exited process,
// available after a call to Wait or Run.
ProcessState *os.ProcessState
// contains filtered or unexported fields
}

介绍如下:

1
2
3
Cmd represents an external command being prepared or run.

A Cmd cannot be reused after calling its Run, Output or CombinedOutput methods.

可以看到局限性在于只能run一次。不过没关系,对于大部分情况是够用了的。

那么我们应该怎么去用这个东西呢?

go 标准库提供了这么一个函数用来创建cmd:

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

这个函数返回了一个填充了PathArgs的Cmd对象

如果说在name中有路径分隔符(path separator),那么Command会自动在系统设定的path中寻找这个命令,如果没找到的话就直接把输入的name当作path。

我们只要用

1
2
cmd := exec.Command("ls", "-a", "-l", "-h")  //实际可以直接写成-alh
b, err := cmd.CombinedOutput()

这样就可以获得到cmd执行后的结果。

Cmd上有好几个函数可以做到获得输出,比较常用的是

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

这个函数会先运行(run)这个cmd,然后再把stdout和stderr的输出放在一起返回。

除此之外,还有比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
func (c *Cmd) Output() ([]byte, error)

func (c *Cmd) Run() error

func (c *Cmd) Start() error

func (c *Cmd) StderrPipe() (io.ReadCloser, error)

func (c *Cmd) StdinPipe() (io.WriteCloser, error)

func (c *Cmd) StdoutPipe() (io.ReadCloser, error)

func (c *Cmd) Wait() error

等函数可以使用,具体可以查询标准库

除此之外,还有比如StartProcess之类的函数可以使用,大家可以自行尝试一下。