有时候我们要在程序里面运行一个程序, 并对其进行管理.
以下是部分经验分享.
1. Run()
和 Start()
的区别
cmd := exec.Command("your-command", "your-args")
err := cmd.Start()
// err := cmd.Run()
查看源码:
// Run starts the specified command and waits for it to complete.
//
// The returned error is nil if the command runs, has no problems
// copying stdin, stdout, and stderr, and exits with a zero exit
// status.
//
// If the command starts but does not complete successfully, the error is of
// type *ExitError. Other error types may be returned for other situations.
//
// If the calling goroutine has locked the operating system thread
// with runtime.LockOSThread and modified any inheritable OS-level
// thread state (for example, Linux or Plan 9 name spaces), the new
// process will inherit the caller's thread state.
func (c *Cmd) Run() error {
if err := c.Start(); err != nil {
return err
}
return c.Wait()
}
Run()
调用的就是 Start()
但是还调用了一个 Wait()
方法.
Start()
会执行一个命令, 但是不会等待命令执行完成Run()
会执行一个命令, 并等待命令执行完成
2. 运行结果
CombinedOutput()
方法会执行命令, 并且返回结果. 但是会等待命令执行完成后才返回结果. 它调用了 Run()
如果我们希望命令执行的过程中实时输出命令运行中的输出, 可以使用以下方式:
// 设置输出和错误的处理方式
cmd := exec.Command("your-command", "your-args")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
3. 杀死一个命令
以下命令就可以杀死运行的命令
cmd.Process.Kill()
如果我们运行的命令又创建了子进程, 甚至更甚层次的进程, 这个命令只能杀死我们运行的那个进程, 子进程依然会继续运行.
如果想彻底杀死运行的命令以及其创建的子进程, 可以使用以下方式:
cmd := exec.Command("your-command", "your-args")
// 创建新的进程组
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
err := cmd.Run()
if err != nil {
panic(err)
}
pgid, err := syscall.Getpgid(cmd.Process.Pid)
if err != nil {
panic(err)
}
syscall.Kill(-pgid, syscall.SIGKILL)
需要注意的是要添加 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
否则会连我们执行命令的程序也杀死.
程序进程的信号可以参考Golang 程序进程信号汇总
但是这个又会有一个新的问题, 就是执行命令的程序终止运行后, 程序中创建的进程组还在运行. 这时要做多一步, 监听进程的信号, 如果是终止运行, 则首先杀死运行中的程序, 再退出.
// 创建一个接收信号的 channel
sigs := make(chan os.Signal, 1)
// 使用 signal.Notify 注册你想要监听的信号
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
// 在一个 goroutine 中等待信号
sig := <-sigs
fmt.Println()
fmt.Println(sig)
os.Exit(0)
}()
fmt.Println("awaiting signal")
select {}