Go子进程

Go语言的os/exec包封装了调用外部可执行程序的操作。它包装了os.StartProcess,以便更容易映射stdinstdout,使用管道连接I/O,并进行其它调整。
os/exec假定运行在Linux环境,windows某些操作可能无法执行,此包的操作也无法在Go Playground上运行。

查找可执行程序

func LookPath(file string) (string, error)

示例

package main

import (
	"os/exec"

	log "github.com/sirupsen/logrus"
)

func main() {
	s, err := exec.LookPath("ls")
	if err != nil {
		log.Error(err)
	}
	log.Info(s)
	log.Info("------------------------------------")
}

启动一个子进程

// 开始执行命令,但是并不等待程序执行结束
func (c *Cmd) Start() error

// 开始执行命令,并等待程序执行结束
func (c *Cmd) Run() error

// 开始执行命令,并等待程序执行结束,如果程序有异常或者输出,则合并返回
func (c *Cmd) CombinedOutput() ([]byte, error)

// 开始执行命令,并等待程序执行结束,如果程序有输出,则返回
func (c *Cmd) Output() ([]byte, error)

Start 示例

package main

import (
	"io/ioutil"
	"os/exec"
	"path/filepath"

	log "github.com/sirupsen/logrus"
)

func main() {
	s, err := exec.LookPath("ifconfig")
	if err != nil {
		log.Fatal("Look Up Command ERROR : ", err)
	}

	folder, filename := filepath.Split(s)

	cmd := exec.Cmd{
		Path: filename,
		Dir:  folder,
	}
	reader, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal("Stdout Pipe ERROR : ", err)
	}

	if err := cmd.Start(); err != nil {
		log.Fatal("Start ERROR : ", err)
	}

	blist, err := ioutil.ReadAll(reader)
	if err != nil {
		log.Fatal("Read Result ERROR : ", err)
	}
	log.Info(string(blist))
	err = cmd.Wait()
	if err != nil {
		log.Fatal("Wait ERROR : ", err)
	}
	log.Info("------------------------------------")
}

Run 示例

package main

import (
	"os/exec"
	"path/filepath"

	log "github.com/sirupsen/logrus"
)

func init() {
	log.SetFormatter(&log.TextFormatter{
		FullTimestamp: true,
	})
}

func main() {
	s, err := exec.LookPath("ifconfig")
	if err != nil {
		log.Fatal("Look Up Command ERROR : ", err)
	}
	folder, filename := filepath.Split(s)
	cmd := exec.Cmd{
		Path: filename,
		Dir:  folder,
	}
	if err := cmd.Run(); err != nil {
		log.Fatal("Wait ERROR : ", err)
	}
	log.Info("------------------------------------")
}

Output 示例

package main

import (
	"os/exec"
	"path/filepath"

	log "github.com/sirupsen/logrus"
)

func init() {
	log.SetFormatter(&log.TextFormatter{
		FullTimestamp: true,
	})
}

func main() {
	s, err := exec.LookPath("ifconfig")
	if err != nil {
		log.Fatal("Look Up Command ERROR : ", err)
	}
	folder, filename := filepath.Split(s)
	cmd := exec.Cmd{
		Path: filename,
		Dir:  folder,
	}
	var blist []byte
	if blist, err = cmd.Output(); err != nil {
		log.Fatal("Wait ERROR : ", err)
	}
	log.Info("Result :\n", string(blist))
	log.Info("------------------------------------")
}

因为ifconfig在部分操作系统(比如debian)做为root命令来使用,所以,需要使用sudo ./xxxx来执行编译后的程序。

结束进程

cmd.Process.Kill()
cmd.Wait()

在结束子进程时,父进程还没有释放资源,所以子进程不能完成资源清理工作。此时可以使用cmd.Wait()完成子进程资源清理工作。

示例

package main

import (
	"io/ioutil"
	"os/exec"
	"path/filepath"
	"time"

	log "github.com/sirupsen/logrus"
)

func init() {
	log.SetFormatter(&log.TextFormatter{
		FullTimestamp: true,
	})
}

func main() {
	s, err := exec.LookPath("top")
	if err != nil {
		log.Fatal("Look Up Command ERROR : ", err)
	}

	folder, filename := filepath.Split(s)

	cmd := exec.Cmd{
		Path: filename,
		Dir:  folder,
	}
	reader, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal("Stdout Pipe ERROR : ", err)
	}

	if err := cmd.Start(); err != nil {
		log.Fatal("Start ERROR : ", err)
	}
	log.Info("top run with pid : ", cmd.Process.Pid)

	blist, err := ioutil.ReadAll(reader)
	if err != nil {
		log.Fatal("Read Result ERROR : ", err)
	}
	log.Info(string(blist))

	time.Sleep(5 * time.Second)

	err = cmd.Process.Kill()
	if err != nil {
		log.Fatal("Kill Sub Process ERROR : ", err)
	}

	log.Info("Killed")

	time.Sleep(30 * time.Second)

	err = cmd.Wait()
	if err != nil {
		log.Fatal("Wait ERROR : ", err)
	}
	log.Info("------------------------------------")
}

上面的示例中,当终端输出Killed时,在另一个终端执行ps -ef | grep sub_pid,会得到如下输出:

$ ps -ef | grep 3647
USER      3647   3641  0 18:53 pts/0    00:00:00 [top] <defunct>
USER      3649   3636  0 18:54 pts/1    00:00:00 grep 3647