I'm trying to read and write to an exec.Command
, but I'm struggling with piping.
This first one is a example of the behaviour I want. The go application just proxies stdin, stdout and stderr to the command:
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("bash", "-i")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
fmt.Println("Done")
}
Running this is like:
pensarando@home: ~/go/src/test$ ./test
pensarando@home: ~/go/src/test$ echo "hello"
hello
pensarando@home: ~/go/src/test$ exit
Done
pensarando@home: ~/go/src/test$
But this one does not work at all:
package main
import (
"fmt"
"io"
"os"
"os/exec"
"sync"
)
func main() {
cmd := exec.Command("bash", "-i")
in, _ := cmd.StdinPipe()
out, _ := cmd.StdoutPipe()
err, _ := cmd.StderrPipe()
exit := make(chan struct{})
done := func() {
in.Close()
out.Close()
err.Close()
cmd.Wait()
close(exit)
}
var once sync.Once
go func() {
io.Copy(os.Stdout, out)
fmt.Println("done stdout")
once.Do(done)
}()
go func() {
io.Copy(in, os.Stdin)
fmt.Println("done stdin")
once.Do(done)
}()
go func() {
io.Copy(os.Stderr, err)
fmt.Println("done stderr")
once.Do(done)
}()
cmd.Start()
<-exit
fmt.Println("Done")
}
When I run this one from the terminal, the output is for example:
pensarando@home:~/go/src/test$ go build
pensarando@home:~/go/src/test$ ./test
pensarando@home:~/go/src/test$ echo "Hello"
[1]+ Stopped ./test
pensarando@home:~/go/src/test$ echo "Hello"
Hello
pensarando@home:~/go/src/test$ fg
./test
done stdin
done stdout
done stderr
Done
pensarando@home:~/go/src/test$
What exactly happens here?