近期有用Golang调用外部命令的需求,期间也踩了些坑。这里整理了一下代码发出来。目前开发用的操作系统是Ubuntu 22,Go的版本是1.18.3。
type Result struct {
Code int
StdOut []byte
ErrOut []byte
}
func ExecuteCmd(dur time.Duration, args ...string) (result *Result, err error) {
ctx, cancel := context.WithTimeout(context.Background(), dur)
defer cancel()
if len(args) == 0 {
err = errors.New("invalid parameters, command is empty")
return
}
var stdout, stderr bytes.Buffer
cmd := exec.Command("/bin/bash", "-c", strings.Join(args, " "))
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
if err = cmd.Start(); err != nil {
return
}
notify := make(chan struct{})
go func() {
err = cmd.Wait()
close(notify)
}()
select {
case <-notify:
case <-ctx.Done():
if err = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL); err == nil {
err = errors.New("execution timeout")
}
// return directly when execution timeout
return
}
result = new(Result)
result.Code = cmd.ProcessState.ExitCode()
if stdout.Len() > 0 {
result.StdOut = stdout.Bytes()
}
if stderr.Len() > 0 {
result.ErrOut = stderr.Bytes()
}
return
}
1、golang本来有一个exec.CommandContext函数用于传入带超时控制的context,用于实现超时控制。但这个函数对超时控制不是很好,有可能会挂起。同时也发生过进程无法删除,成为僵尸进程。
2、SysProcAttr的Setpgid设置为true,主要是为了超时杀掉主进程时可以将命令行进程一起杀掉,避免僵尸进程。
3、cmd.Wait()调用放在go协程中主要是为了避免被调用的命令行挂起时杀进程异常。