Home > database >  Send stdout of running command to its stdin in go
Send stdout of running command to its stdin in go

Time:08-14

I have a somewhat challenging situation where I need to write into a system command stdin the same stdout it outputs (in another running program), here's an example program that represents what I mean:

package main

import (
    "bufio"
    "fmt"
    "math/rand"

    "os"
)

func main() {
    rand.Seed(time.Now().Unix())
    var greetings []string = []string{"hi", "hola", "bonjour", "hallo", "whats up"}
    var greeting string = greetings[rand.Intn(len(greetings))]
    fmt.Println(greeting)

    reader := bufio.NewReader(os.Stdin)
    message, _ := reader.ReadString('\n')
    if message == greeting "\n" {
        fmt.Println("nice to meet you!")
    } else {
        fmt.Println("oops!")
    }
}

Since you greet with a random greeting, you have to read the stdout, send it to stdin and also capture if it was the correct answer or not. I've tried with stdinpipes but it freezes waiting for the stdin close since I think that only works for the start of the command run only, so for a running program it hasn't been working for me...

I appreciate any help!

EDIT

I wanted to add sort of what I was trying to do, I've tried without channels as well but it didn't seem to make a difference on the outcome, it just freezes waiting for stdin to close and I need to get first stdout before closing stdin since it consists of it:

package main

import (
    "io"
    "os/exec"
)

func main() {
    cmd := exec.Command("./executable_program")
    stdout, _ := cmd.StdoutPipe()
    stdin, _ := cmd.StdinPipe()

    var c chan []byte = make(chan []byte)

    cmd.Start()
    go func() {
        b, _ := io.ReadAll(stdout)
        c <- b
    }()

    stdin.Write(<-c)
    stdin.Close()

    cmd.Wait()
}

CodePudding user response:

You can use a pipe to join the stdout to the stdin of the program that you execute:

package main

import (
    "io"
    "os/exec"
)

func main() {
    r, w := io.Pipe()

    cmd := exec.Command("<name-of-program-to-run>")
    cmd.Stdin = r 
    cmd.Stdout = w 

    cmd.Run()
}

To see this in action, first let's prepare a test program to be executed by the program above. This test program simply prints a line to stdout, and then reads each line of stdin and prints it to stdout until stdin is closed.

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    fmt.Fprint(os.Stdout, "priming the pump!\n")
    s := bufio.NewScanner(os.Stdin)
    for s.Scan() {
        line := s.Text()
        fmt.Fprint(os.Stdout, line "\n")
    }   
}

Then, we modify our initial program to print the bytes traversing through the pipe so we see what's going on.

package main

import (
    "fmt"
    "io"
    "os/exec"
)

func main() {
    r, w := io.Pipe()

    sr := &readSpy{r: r}
    wr := &writeSpy{w: w}

    cmd := exec.Command("./test-program")
    cmd.Stdin = sr
    cmd.Stdout = wr

    cmd.Run()
}

type readSpy struct {
    r io.Reader
}

func (s *readSpy) Read(d []byte) (int, error) {
    size, err := s.r.Read(d)
    fmt.Println("readSpy read", string(d[:size]))
    return size, err
}

type writeSpy struct {
    w io.Writer
}

func (s *writeSpy) Write(d []byte) (int, error) {
    size, err := s.w.Write(d)
    fmt.Println("writeSpy wrote", string(d[:size]))
    return size, err
}

Running the above, you will see the following getting printed in a infinite loop, which makes sense since the priming the pump! string is printed to stdout and fed right back to the stdin of the test program:

writeSpy wrote priming the pump!

readSpy read priming the pump!

...repeated forever...
  • Related