package sys import ( "bytes" "os/exec" "strings" "syscall" "time" ) func CmdOutString(name string, arg ...string) (string, error) { bs, err := CmdOutBytes(name, arg...) if err != nil { return "", err } return string(bs), nil } func CmdOutBytes(name string, arg ...string) ([]byte, error) { cmd := exec.Command(name, arg...) return cmd.CombinedOutput() } func CmdOutTrim(name string, arg ...string) (out string, err error) { out, err = CmdOutString(name, arg...) if err != nil { return } return strings.TrimSpace(string(out)), nil } func CmdRun(name string, arg ...string) error { cmd := exec.Command(name, arg...) return cmd.Run() } // CmdRunT Command run with timeout func CmdRunT(timeout time.Duration, name string, arg ...string) (output string, err error, istimeout bool) { cmd := exec.Command(name, arg...) cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} var b bytes.Buffer cmd.Stdout = &b cmd.Stderr = &b cmd.Start() err, istimeout = WrapTimeout(cmd, timeout) output = b.String() return } func WrapTimeout(cmd *exec.Cmd, timeout time.Duration) (error, bool) { var err error done := make(chan error) go func() { done <- cmd.Wait() }() select { case <-time.After(timeout): go func() { <-done // allow goroutine to exit }() // IMPORTANT: cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} is necessary before cmd.Start() err = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) return err, true case err = <-done: return err, false } }