1

I'm trying to read stdout from a long-running (blocking / shell-like) command with the ultimate goal of creating a sidecar process that Go can interact with.

I have the following MCVE:

func main() {
    var err error
    // Build the long-running command
    args := []string{"-i0", "-o0", "-e0", "/usr/local/bin/ipython"}
    cmd := exec.Command("stdbuf", args...)

    // Keep the stdin file descriptor open
    // Spawned command should block waiting for input
    _, err = cmd.StdinPipe()
    if err != nil {
        panic(err)
    }

    // Setup pipe of `Reader` type
    var stdoutBuf []byte
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        panic(err)
    }

    // Start the command
    if err = cmd.Start(); err != nil {
        panic(err)
    }

    go func() {
        for {
            // Asynchronously continue reading from stdout
            n, err := stdout.Read(stdoutBuf)
            fmt.Println(n)
            if err != nil {
                panic(err)
            }
            fmt.Println(stdoutBuf)
            time.Sleep(time.Millisecond * 2000)
        }
    }()

    // Block forever
    if err = cmd.Wait(); err != nil {
        panic(err)
    }
}

Semantically this approach seems like it would work. I would expect that stdout.Read would return something (ipython shell preamble), but n is always 0.

  1. Why does Reader.Read in this example never read anything?
  2. Somewhat related question: is there a way I can block on reading StdoutPipe if there is nothing to be read?

I don't think anything is wrong with the process itself (which would cause an invalid file descriptor read) because Start doesn't panic and it seem like cmd.Wait blocks forever.

James Taylor
  • 6,158
  • 8
  • 48
  • 74

1 Answers1

0

Why does Reader.Read in this example never read anything?

Because you are reading into a buffer of size 0, which is what var stdoutBuf []byte creates. Instead, use something like:

stdoutBuf := make([]byte, 4096)

You may also want to use bufio.

Somewhat related question: is there a way I can block on reading StdoutPipe if there is nothing to be read?

stdout.Read already blocks, as is typical (though not required) for an io.Reader:

If some data is available but not len(p) bytes, Read conventionally returns what is available instead of waiting for more. [...] Implementations of Read are discouraged from returning a zero byte count with a nil error, except when len(p) == 0.

Vasiliy Faronov
  • 11,840
  • 2
  • 38
  • 49