2

I have a function like this:

package main

import (
    "fmt"
)

// PrintSomething prints some thing
func PrintSomething() {
    fmt.Println("print something")
}

func main() {
    PrintSomething()
}

How do I wrap PrintSomething to another function call CaptureSomething to save the string "print something" to a variable and return it?

2
  • 2
    @Himanshu: How would that help? Commented Jul 5, 2018 at 5:56
  • @ThunderCat: All those solutions have fatal flaws. Commented Jul 5, 2018 at 6:10

2 Answers 2

10

Create pipe and set stdout to the pipe writer. Start a goroutine to copy the pipe reader to a buffer. When done, close the pipe writer and wait for goroutine to complete reading. Return the buffer as a string.

// capture replaces os.Stdout with a writer that buffers any data written 
// to os.Stdout. Call the returned function to cleanup and get the data
// as a string.
func capture() func() (string, error) {
    r, w, err := os.Pipe()
    if err != nil {
        panic(err)
    }

    done := make(chan error, 1)

    save := os.Stdout
    os.Stdout = w

    var buf strings.Builder

    go func() {
        _, err := io.Copy(&buf, r)
        r.Close()
        done <- err
    }()

    return func() (string, error) {
        os.Stdout = save
        w.Close()
        err := <-done
        return buf.String(), err
    }

}

Use it like this:

done := capture()
fmt.Println("Hello, playground")
capturedOutput, err := done()
if err != nil {
    // handle error
}

playground example

Sign up to request clarification or add additional context in comments.

1 Comment

I'd make done transmit error instead of struct{} and would send on it the error returned from io.Copy or r.Close, if any, or just nil otherwise. Then I'd check what <-done returns. Well, in this case errors are unlikely but still it's better to blow up on an unexpected error than ignore it silently.
1

Use one of these, whichever works for you:

package main

import (
    "bytes"
    "fmt"
    "io"
    "os"
    "strings"
)

func PrintSomething() {
    fmt.Println("print something")
}

func PrintSomethingBig() {
    for i := 0; i < 100000; i++ {
        fmt.Println("print something")
    }
}

func PrintSomethingOut(out io.Writer) {
    fmt.Fprintln(out, "print something to io.Writer")
}

func PrintSomethingString() string {
    return fmt.Sprintln("print something into a string")
}

// not thread safe
// modified by [email protected] from original at http://craigwickesser.com/2015/01/capture-stdout-in-go/
func captureStdout(f func()) string {
    old := os.Stdout
    r, w, _ := os.Pipe()
    os.Stdout = w

    go func() {
        f()
        w.Close()
    }()

    buf := &bytes.Buffer{}
    // Will complete when the goroutine calls w.Close()
    io.Copy(buf, r)

    // Clean up.
    os.Stdout = old
    r.Close()

    return buf.String()
}

func main() {
    str1 := &strings.Builder{}
    str2 := PrintSomethingString()
    PrintSomethingOut(str1)
    PrintSomethingOut(os.Stdout)
    str3 := captureStdout(PrintSomething)
    str4 := captureStdout(PrintSomethingBig)
    fmt.Println("string 1 is", str1)
    fmt.Println("string 2 is", str2)
    fmt.Println("string 3 is", str3)
    fmt.Println("string 4 len", len(str4))
}

2 Comments

I you can't control the PrintSomething function, I need a technique to wrap it
@HauMa Added a capture wrap function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.