Created
March 13, 2022 15:05
-
-
Save dsggregory/71ebb1aa67bcf61ab6d05c20530c590c to your computer and use it in GitHub Desktop.
Golang example of an exec streaming the output
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package myexec | |
import ( | |
"bufio" | |
"bytes" | |
"context" | |
"fmt" | |
"io" | |
"os" | |
"os/exec" | |
"sync" | |
"github.com/sirupsen/logrus" | |
) | |
// CapturingPassThroughWriter is a writer that remembers | |
// data written to it and passes it to w | |
type CapturingPassThroughWriter struct { | |
buf bytes.Buffer | |
w io.Writer | |
} | |
// NewCapturingPassThroughWriter creates new CapturingPassThroughWriter | |
func NewCapturingPassThroughWriter(w io.Writer) *CapturingPassThroughWriter { | |
return &CapturingPassThroughWriter{ | |
w: w, | |
} | |
} | |
func (w *CapturingPassThroughWriter) Write(d []byte) (int, error) { | |
w.buf.Write(d) | |
return w.w.Write(d) | |
} | |
// Bytes returns bytes written to the writer | |
func (w *CapturingPassThroughWriter) Bytes() []byte { | |
return w.buf.Bytes() | |
} | |
// DoExec exec a command-line and send stdout to the writer `w`. | |
func DoExec(ctx context.Context, w io.Writer, command ...string) error { | |
cmd := exec.CommandContext(ctx, command[0], command[1:]...) | |
var errStdout, errStderr error | |
stdoutIn, _ := cmd.StdoutPipe() | |
stderrIn, _ := cmd.StderrPipe() | |
//stdout := NewCapturingPassThroughWriter(os.Stdout) | |
stdout := w | |
stderr := NewCapturingPassThroughWriter(os.Stderr) | |
err := cmd.Start() | |
if err != nil { | |
return fmt.Errorf("%w; cmd.Start() failed", err) | |
} | |
var wg sync.WaitGroup | |
wg.Add(1) | |
go func() { | |
_, errStdout = io.Copy(stdout, stdoutIn) | |
wg.Done() | |
}() | |
_, errStderr = io.Copy(stderr, stderrIn) | |
wg.Wait() | |
err = cmd.Wait() | |
// NOTE: exec status != 0 sets err. The "diff" command succeeds with status=1 on a difference. We check that stderr has data before we return an error. | |
if err != nil && stderr.buf.Len() > 0 { | |
return fmt.Errorf("%w; cmd.Run() failed\n%s", err, stderr.buf.String()) | |
} else { | |
err = nil | |
} | |
if errStdout != nil || errStderr != nil { | |
err = fmt.Errorf("failed to capture stdout or stderr") | |
if errStdout != nil { | |
err = fmt.Errorf("%w; %s", errStdout, err.Error()) | |
} | |
if errStderr != nil { | |
err = fmt.Errorf("%w; %s", errStderr, err.Error()) | |
} | |
} | |
return err | |
} | |
func Example() { | |
pr, pw := io.Pipe() | |
go func() { | |
if err := DoExec(context.Background(), pw, "diff", "/tmp/prev", "/tmp/cur"); err != nil { | |
_ = pw.CloseWithError(err) | |
logrus.WithError(err).Error("diff failed") | |
} else { | |
_ = pw.Close() | |
} | |
}() | |
// print the output from the command | |
sc := bufio.NewScanner(pr) | |
for sc.Scan() { | |
fmt.Println(sc.Text()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment