9、os

os 包及其子包 os/exec 提供了创建进程的方法。

一般的,应该优先使用 os/exec 包。因为 os/exec 包依赖 os 包中关键创建进程的 API,为了便于理解,我们先探讨 os 包中和进程相关的部分。

运行外部命令

Go 标准库封装了更好用的包: os/exec,运行外部命令,应该优先使用它,它包装了 os.StartProcess 函数以便更容易的重定向标准输入和输出,使用管道连接 I/O,以及作其它的一些调整。

查找可执行程序

exec.LookPath 函数在 PATH 指定目录中搜索可执行程序,如 file 中有 /,则只在当前目录搜索。该函数返回完整路径或相对于当前路径的一个相对路径。

func LookPath(file string) (string, error)

如果在 PATH 中没有找到可执行文件,则返回 exec.ErrNotFound

Cmd 及其相关方法

Cmd 结构代表一个正在准备或者在执行中的外部命令,调用了 RunOutputCombinedOutput 后,Cmd 实例不能被重用。

type Cmd struct {
    // Path 是将要执行的命令路径。
    // 该字段不能为空(也是唯一一个不能为空的字段),如为相对路径会相对于 Dir 字段。
    // 通过 Command 初始化时,会在需要时调用 LookPath 获得完整的路径。
    Path string

    // Args 存放着命令的参数,第一个值是要执行的命令(Args[0]);如果为空切片或者 nil,使用 {Path} 运行。
    // 一般情况下,Path 和 Args 都应被 Command 函数设定。
    Args []string

    // Env 指定进程的环境变量,如为 nil,则使用当前进程的环境变量,即 os.Environ(),一般就是当前系统的环境变量。
    Env []string

    // Dir 指定命令的工作目录。如为空字符串,会在调用者的进程当前工作目录下执行。
    Dir string

    // Stdin 指定进程的标准输入,如为 nil,进程会从空设备读取(os.DevNull)
    // 如果 Stdin 是 *os.File 的实例,进程的标准输入会直接指向这个文件
    // 否则,会在一个单独的 goroutine 中从 Stdin 中读数据,然后将数据通过管道传递到该命令中(也就是从 Stdin 读到数据后,写入管道,该命令可以从管道读到这个数据)。在 goroutine 停止数据拷贝之前(停止的原因如遇到 EOF 或其他错误,或管道的 write 端错误),Wait 方法会一直堵塞。
    Stdin io.Reader

    // Stdout 和 Stderr 指定进程的标准输出和标准错误输出。
    // 如果任一个为 nil,Run 方法会将对应的文件描述符关联到空设备(os.DevNull)
    // 如果两个字段相同,同一时间最多有一个线程可以写入。
    Stdout io.Writer
    Stderr io.Writer

    // ExtraFiles 指定额外被新进程继承的已打开文件,不包括标准输入、标准输出、标准错误输出。
    // 如果本字段非 nil,其中的元素 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
}

Command

一般的,应该通过 exec.Command 函数产生 Cmd 实例:

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

该函数返回一个 *Cmd,用于使用给出的参数执行 name 指定的程序。返回的 *Cmd 只设定了 PathArgs 两个字段。

如果 name 不含路径分隔符,将使用 LookPath 获取完整路径;否则直接使用 name。参数 arg 不应包含命令名。

得到 *Cmd 实例后,接下来一般有两种写法:

  1. 调用 func (c *Cmd) Start() error,接着调用 func (c *Cmd) Wait() error,然后会阻塞直到命令执行完成;
  2. 调用 func (c *Cmd) Run() error,它内部会先调用 Start(),接着调用 Wait()

Output

Output() 更是 Run() 的简便写法,外加获取外部命令的输出。

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

例子:

package main

import (
	"fmt"
	"github.com/axgle/mahonia"
	"os/exec"
)

// ConvertToString src:源字符串;srcCode:源字符串编码;tagCode:目标字符串编码
func ConvertToString(src string, srcCode string, tagCode string) string {

	srcCoder := mahonia.NewDecoder(srcCode)

	srcResult := srcCoder.ConvertString(src)

	tagCoder := mahonia.NewDecoder(tagCode)

	_, cdata, _ := tagCoder.Translate([]byte(srcResult), true)

	result := string(cdata)

	return result
}

func main() {
	cmd := exec.Command("ipconfig")
	b, err := cmd.Output()
	if err != nil {
		fmt.Println(err)
		return
	}
	s := ConvertToString(string(b), "gbk", "utf8")
	fmt.Println(s)
}