标准库os

os/exec

// ExecShell 在指定目录执行一个shell指令,并获得其输出
func ExecShell(dir string, commandName string, arg ...string) (msg string, err error) {
	cmd := exec.Command(commandName, arg...)
	var outbuf, errbuf bytes.Buffer
	cmd.Stdout = &outbuf
	cmd.Stderr = &errbuf
	cmd.Dir = dir
	if err = cmd.Run(); err != nil {
		msg = fmt.Sprintf("shell command: %s %v is failed: %s, caused: %s", cmd.Path, cmd.Args, err, errbuf.String())
		return msg, err
	}
	msg = fmt.Sprintf("Execute shell %s %v finished with output: %s", cmd.Path, cmd.Args, outbuf.String())
	return msg, nil
}

想要以nohup方式启动独立的进程?

// ExecSubProcessBackground 在指定目录后台执行一个shell命令,用于启动一个独立的子进程,即使本进程退出,子进程仍然可以继续运行
func ExecSubProcessBackground(dir string, command string) (msg string, err error) {
	// 像需要后台运行这种复杂的操作,需要用 sh 的 -c 参数,来一次性传一个复杂的指令
	// -c 后面只能接收1个参数,如果传了多个,多余的命令会被直接舍弃
	cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("nohup %s >out.log 2>&1 & echo $! > command.pid", command))
	var outbuf, errbuf bytes.Buffer
	cmd.Stdout = &outbuf
	cmd.Stderr = &errbuf
	cmd.Dir = dir
	if err = cmd.Run(); err != nil {
		msg = fmt.Sprintf("shell command: %s %v is failed: %s, caused: %s", cmd.Path, cmd.Args, err, errbuf.String())
		return msg, err
	}
	msg = fmt.Sprintf("Execute shell %s %v finished with output: %s", cmd.Path, cmd.Args, outbuf.String())
	return msg, nil
}

注:在ExecShell函数也可以达到如上实现的效果,ExecShell("./", "./run.sh"),编写run.sh文件:nohup ./{executable_file} >out.log 2>&1 & echo $! > command.pid

运行交互式shell子进程?

以调用redis-cli为例:

func TestInteractiveShell(t *testing.T) {
	in := bytes.NewBuffer(nil)
	cmd := exec.Command("redis-cli")
	// cmd := exec.Command("bash", "-c", "~/xxx/xxx.sh xxx xxx")
	cmd.Stdin = in
	cmd.Stdout = os.Stdout
	go func() {
		in.WriteString("keys *\n")
		in.WriteString("get stock:0\n")
	}()
	err := cmd.Run()
	assert.Nil(t, err)
}

cmd.Start()和cmd.Run()的区别?

cmd.Start()非阻塞,不会等命令执行。

cmd.Run()是阻塞的,等待命令执行完毕。

kill()的坑

cmd.Process.Kill()  // cmd.Start()之后如果只调用kill会导致子进程成为僵尸进程(通过ps指令能查到 process_name <defunct> 进程)

那么应该怎么办呢?父进程没有等待(调用wait/waitpid)子进程,子进程就会变成一个僵尸进程,那么解决方法就是调用Wait()即可:

if err := cmd.Process.Kill(); err != nil {
	return err
}
go func() {
	if err := cmd.Wait(); err != nil {
		creator.Logger.Warn(err)
	}
}()  // 异步等待

Last updated

Was this helpful?