Skip to content

Instantly share code, notes, and snippets.

@dsggregory
Created March 13, 2022 15:05
Show Gist options
  • Save dsggregory/71ebb1aa67bcf61ab6d05c20530c590c to your computer and use it in GitHub Desktop.
Save dsggregory/71ebb1aa67bcf61ab6d05c20530c590c to your computer and use it in GitHub Desktop.
Golang example of an exec streaming the output
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