~craftyguy/go-cmd

09f29e254cc2bd373d01f4ac7d282309d48b4de4 — Clayton Craft 1 year, 3 months ago 47fc40d
Support writing stdout/stderr to os.File

os/exec's Cmd can optionally write stdout/stderr directly to an os.File,
which is useful for cases when running an app that forks children who
stay alive after the parent has quit. In that situation, Go's runtime
will think the parent is still alive because the pipes for stdout/stderr
would still be open by the child(ren). When stdout/stderr is set to an
os.File, the runtime will consider the parent done when it quits,
regardless of what the child(ren) does.
1 files changed, 15 insertions(+), 1 deletions(-)

M cmd.go
M cmd.go => cmd.go +15 -1
@@ 47,6 47,7 @@ import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"sync"
	"syscall"


@@ 94,6 95,8 @@ type Cmd struct {
	stderrBuf       *OutputBuffer
	stdoutStream    *OutputStream
	stderrStream    *OutputStream
	stdoutFile      *os.File
	stderrFile      *os.File
	status          Status
	statusChan      chan Status   // nil until Start() called
	doneChan        chan struct{} // closed when done running


@@ 153,6 156,9 @@ type Options struct {
	// streaming channels, else lines are dropped silently.
	Streaming bool

	// If File is set, Cmd.Stdout and Cmd.Stderr are written directly to it.
	File *os.File

	// BeforeExec is a list of functions called immediately before starting
	// the real command. These functions can be used to customize the underlying
	// os/exec.Cmd. For example, to set SysProcAttr.


@@ 203,6 209,11 @@ func NewCmdOptions(options Options, name string, args ...string) *Cmd {
		c.stderrStream.SetLineBufferSize(int(options.LineBufferSize))
	}

	if options.File != nil {
		c.stdoutFile = options.File
		c.stderrFile = options.File
	}

	if len(options.BeforeExec) > 0 {
		c.beforeExecFuncs = []func(cmd *exec.Cmd){}
		for _, f := range options.BeforeExec {


@@ 409,7 420,7 @@ func (c *Cmd) run(in io.Reader) {
	// Set exec.Cmd.Stdout and .Stderr to our concurrent-safe stdout/stderr
	// buffer, stream both, or neither
	switch {
	case c.stdoutBuf != nil && c.stdoutStream != nil: // buffer and stream
	case c.stdoutBuf != nil && c.stdoutStream != nil && c.stdoutFile != nil: // buffer and stream
		cmd.Stdout = io.MultiWriter(c.stdoutStream, c.stdoutBuf)
		cmd.Stderr = io.MultiWriter(c.stderrStream, c.stderrBuf)
	case c.stdoutBuf != nil: // buffer only


@@ 418,6 429,9 @@ func (c *Cmd) run(in io.Reader) {
	case c.stdoutStream != nil: // stream only
		cmd.Stdout = c.stdoutStream
		cmd.Stderr = c.stderrStream
	case c.stdoutFile != nil: // file only
		cmd.Stdout = c.stdoutFile
		cmd.Stderr = c.stderrFile
	default: // no output (cmd >/dev/null 2>&1)
		cmd.Stdout = nil
		cmd.Stderr = nil