Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proxy exec.Cmd Stdout / Stderr without losing TTY

I've got the following code that executes an arbitrary shell command and pipes the stdout and stderr to the terminal.:

c := exec.Command("/bin/sh", "-c", cmd)
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr

However, I need to process the output before I print it, so I've wrapped it with a proxy io.Writer interface:

type ProxyWriter struct {
    file   *os.File
}

func NewProxyWriter(file *os.File) *ProxyWriter {
    return &ProxyWriter{
        file: file,
    }
}

func (w *ProxyWriter) Write(p []byte) (int, error) {
    // ... do something with bytes first
    fmt.Fprintf(w.file, "%s", string(p))
    return len(p), nil
}

So the original code is now:

c := exec.Command("/bin/sh", "-c", cmd)
c.Stdin = os.Stdin
c.Stdout = NewProxyWriter(os.Stdout)
c.Stderr = NewProxyWriter(os.Stderr)

This works for the most part, however, stdout and stderr no longer seem to qualify as a TTY. Any previously styled or colored output is no longer styled or colored.

I've confirmed that this is not an issue of my ProxyWriter simply messing with the formatting by setting the command to the following, which outputs the colored text correctly.

c := exec.Command("echo", "\033[0;31mTEST\033[0m")

A more explicit test is:

c := exec.Command("/bin/sh", "-c", "if [ -t 1 ] ; then echo \"terminal\"; else echo \"not a terminal\"; fi")

Which outputs:

not a terminal

Is there anyway I can wrap the commands stdout / stderr without losing the TTY status?

like image 595
kbirk Avatar asked May 24 '26 20:05

kbirk


1 Answers

Replace

func (w *ProxyWriter) Write(p []byte) (int, error) {
    // ... do something with bytes first
    fmt.Fprintf(w.file, "%s", string(p))
    return len(p), nil
}

To

func (w *ProxyWriter) Write(p []byte) (int, error) {
    return w.Write(p)
}

fmt.Fprintf have some logic to avoid terminal breaking.

like image 177
mattn Avatar answered May 27 '26 11:05

mattn