diff --git a/internal/externalcmd/cmd.go b/internal/externalcmd/cmd.go index 522f8f18..0d4d8da4 100644 --- a/internal/externalcmd/cmd.go +++ b/internal/externalcmd/cmd.go @@ -33,6 +33,8 @@ func NewCmd( env Environment, onExit func(int), ) *Cmd { + // replace variables in both Linux and Windows, in order to allow using the + // same commands on both of them. for key, val := range env { cmdstr = strings.ReplaceAll(cmdstr, "$"+key, val) } @@ -62,28 +64,30 @@ func (e *Cmd) run() { defer e.pool.wg.Done() for { - ok := func() bool { - c, ok := e.runInner() - if !ok { - return false - } - - e.onExit(c) - - if !e.restart { - <-e.terminate - return false - } - - select { - case <-time.After(restartPause): - return true - case <-e.terminate: - return false - } - }() + ok := e.runInner() if !ok { break } } } + +func (e *Cmd) runInner() bool { + c, ok := e.runOSSpecific() + if !ok { + return false + } + + e.onExit(c) + + if !e.restart { + <-e.terminate + return false + } + + select { + case <-time.After(restartPause): + return true + case <-e.terminate: + return false + } +} diff --git a/internal/externalcmd/cmd_unix.go b/internal/externalcmd/cmd_unix.go index cf23740d..f3691473 100644 --- a/internal/externalcmd/cmd_unix.go +++ b/internal/externalcmd/cmd_unix.go @@ -11,13 +11,13 @@ import ( "github.com/kballard/go-shellquote" ) -func (e *Cmd) runInner() (int, bool) { - cmdparts, err := shellquote.Split(e.cmdstr) +func (e *Cmd) runOSSpecific() (int, bool) { + cmdParts, err := shellquote.Split(e.cmdstr) if err != nil { return 0, true } - cmd := exec.Command(cmdparts[0], cmdparts[1:]...) + cmd := exec.Command(cmdParts[0], cmdParts[1:]...) cmd.Env = append([]string(nil), os.Environ()...) for key, val := range e.env { diff --git a/internal/externalcmd/cmd_win.go b/internal/externalcmd/cmd_win.go index a1dfc3b4..bf3013d9 100644 --- a/internal/externalcmd/cmd_win.go +++ b/internal/externalcmd/cmd_win.go @@ -6,17 +6,37 @@ package externalcmd import ( "os" "os/exec" + "strings" + "syscall" "github.com/kballard/go-shellquote" ) -func (e *Cmd) runInner() (int, bool) { - cmdparts, err := shellquote.Split(e.cmdstr) - if err != nil { - return 0, true - } +func (e *Cmd) runOSSpecific() (int, bool) { + var cmd *exec.Cmd - cmd := exec.Command(cmdparts[0], cmdparts[1:]...) + // from Golang documentation: + // On Windows, processes receive the whole command line as a single string and do their own parsing. + // Command combines and quotes Args into a command line string with an algorithm compatible with + // applications using CommandLineToArgvW (which is the most common way). Notable exceptions are + // msiexec.exe and cmd.exe (and thus, all batch files), which have a different unquoting algorithm. + // In these or other similar cases, you can do the quoting yourself and provide the full command + // line in SysProcAttr.CmdLine, leaving Args empty. + if strings.HasPrefix(e.cmdstr, "cmd ") || strings.HasPrefix(e.cmdstr, "cmd.exe ") { + args := strings.TrimPrefix(strings.TrimPrefix(e.cmdstr, "cmd "), "cmd.exe ") + + cmd = exec.Command("cmd.exe") + cmd.SysProcAttr = &syscall.SysProcAttr{ + CmdLine: args, + } + } else { + cmdParts, err := shellquote.Split(e.cmdstr) + if err != nil { + return 0, true + } + + cmd = exec.Command(cmdParts[0], cmdParts[1:]...) + } cmd.Env = append([]string(nil), os.Environ()...) for key, val := range e.env { @@ -26,7 +46,7 @@ func (e *Cmd) runInner() (int, bool) { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - err = cmd.Start() + err := cmd.Start() if err != nil { return 0, true }