package cmdext import ( "bufio" "os/exec" "time" ) type CommandResult struct { StdOut string StdErr string StdCombined string ExitCode int CommandTimedOut bool } func run(opt CommandRunner) (CommandResult, error) { cmd := exec.Command(opt.program, opt.args...) cmd.Env = append(cmd.Env, opt.env) stdoutPipe, err := cmd.StdoutPipe() if err != nil { return CommandResult{}, err } stderrPipe, err := cmd.StderrPipe() if err != nil { return CommandResult{}, err } err = cmd.Start() if err != nil { return CommandResult{}, err } errch := make(chan error, 1) go func() { errch <- cmd.Wait() }() combch := make(chan string, 32) stopCombch := make(chan bool) stdout := "" go func() { scanner := bufio.NewScanner(stdoutPipe) for scanner.Scan() { txt := scanner.Text() stdout += txt combch <- txt } }() stderr := "" go func() { scanner := bufio.NewScanner(stderrPipe) for scanner.Scan() { txt := scanner.Text() stderr += txt combch <- txt } }() defer func() { stopCombch <- true }() stdcombined := "" go func() { for { select { case txt := <-combch: stdcombined += txt case <-stopCombch: return } } }() var timeoutChan <-chan time.Time = make(chan time.Time, 1) if opt.timeout != nil { timeoutChan = time.After(*opt.timeout) } select { case <-timeoutChan: _ = cmd.Process.Kill() return CommandResult{ StdOut: stdout, StdErr: stderr, StdCombined: stdcombined, ExitCode: -1, CommandTimedOut: true, }, nil case err := <-errch: if exiterr, ok := err.(*exec.ExitError); ok { return CommandResult{ StdOut: stdout, StdErr: stderr, StdCombined: stdcombined, ExitCode: exiterr.ExitCode(), CommandTimedOut: false, }, nil } else if err != nil { return CommandResult{}, err } else { return CommandResult{ StdOut: stdout, StdErr: stderr, StdCombined: stdcombined, ExitCode: 0, CommandTimedOut: false, }, nil } } }