Reputation: 11431
I am running an external process via exec.Command()
and I want the stdout from the command to be printed as well as written to file, in real time (similar to using tee
from a command-line) .
I can achieve this with a scanner and a writer:
cmd := exec.Command("mycmd")
cmdStdOut, _ := cmd.StdoutPipe()
s := bufio.NewScanner(cmdStdOut)
f, _ := os.Create("stdout.log")
w := bufio.NewWriter(f)
go func() {
for s.Scan(){
t := s.Text()
fmt.Println(t)
fmt.Fprint(w, t)
w.Flush()
}
}
Is there a more idiomatic way to do this that avoids clobbering Scan
and Flush
?
Upvotes: 0
Views: 4404
Reputation: 11
Ignoring errors for brevity. As stated by other answers, you could use io.MultiWriter in an io.Copy, but when you are dealing with stdout of exec.Cmd, you need to be aware of Wait closing the pipes as soon as the command terminates, as stated by the documentation (https://golang.org/pkg/os/exec/#Cmd.StdoutPipe).
Wait will close the pipe after seeing the command exit, so most callers need not close the pipe themselves. It is thus incorrect to call Wait before all reads from the pipe have completed.
Ignoring this could lead to some portions of the output not being read, and is therefore lost. Instead, do not use Run, but instead use Start and Wait. eg.
package main
import (
"io"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("date")
stdout, _ := cmd.StdoutPipe()
f, _ := os.Create("stdout.log")
cmd.Start()
io.Copy(io.MultiWriter(f, os.Stdout), stdout)
cmd.Wait()
}
This will ensure everything is read from stdout and close all pipes afterwards.
Upvotes: 1
Reputation: 31691
Assign a multiwriter to the commmand's stdout that writes to a file and to a pipe. You can then use the pipe's read end to follow the output.
This example behaves similar to the tee
tool:
package main
import (
"io"
"os"
"os/exec"
)
func main() {
var f *os.File // e.g. os.Create, os.Open
r, w := io.Pipe()
defer w.Close()
cmd := exec.Command("mycmd")
cmd.Stdout = io.MultiWriter(w, f)
// do something with the output while cmd is running by reading from r
go io.Copy(os.Stdout, r)
cmd.Run()
}
Alternative with StdoutPipe:
package main
import (
"io"
"os"
"os/exec"
)
func main() {
var f *os.File
cmd := exec.Command("date")
stdout, _ := cmd.StdoutPipe()
go io.Copy(io.MultiWriter(f, os.Stdout), stdout)
cmd.Run()
}
Upvotes: 4