duncanhall
duncanhall

Reputation: 11431

Write stdout stream to file

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

Answers (2)

Kaese
Kaese

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

Peter
Peter

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

Related Questions