在 Go 中,如何将函数的 stdout 捕获到字符串中?

问题描述 投票:0回答:5

例如,在 Python 中,我可以执行以下操作:

realout = sys.stdout
sys.stdout = StringIO.StringIO()
some_function() # prints to stdout get captured in the StringIO object
result = sys.stdout.getvalue()
sys.stdout = realout

你能在 Go 中做到这一点吗?

go stdout
5个回答
78
投票

我同意您应该使用

fmt.Fprint
功能(如果您可以管理的话)。但是,如果您不控制要捕获其输出的代码,您可能没有该选项。

Mostafa 的答案是有效的,但如果你想在没有临时文件的情况下完成它,你可以使用 os.Pipe。这是一个与 Mostafa 等效的示例,其中一些代码受到 Go 测试包的启发。

package main

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

func print() {
    fmt.Println("output")
}

func main() {
    old := os.Stdout // keep backup of the real stdout
    r, w, _ := os.Pipe()
    os.Stdout = w

    print()

    outC := make(chan string)
    // copy the output in a separate goroutine so printing can't block indefinitely
    go func() {
        var buf bytes.Buffer
        io.Copy(&buf, r)
        outC <- buf.String()
    }()

    // back to normal state
    w.Close()
    os.Stdout = old // restoring the real stdout
    out := <-outC

    // reading our temp stdout
    fmt.Println("previous output:")
    fmt.Print(out)
}

27
投票

这个答案与之前的答案类似,但使用 io/ioutil 看起来更干净。

https://go.dev/play/p/BmgcoE70QO5

package main

import (
  "fmt"
  "io"
  "os"
)

func main() {
  rescueStdout := os.Stdout
  r, w, _ := os.Pipe()
  os.Stdout = w

  fmt.Println("Hello, playground") // this gets captured

  w.Close()
  out, _ := io.ReadAll(r)
  os.Stdout = rescueStdout
  
  fmt.Printf("Captured: %s", out) // prints: Captured: Hello, playground
}

16
投票

我不推荐这样做,但你可以通过改变

os.Stdout
来实现。由于此变量的类型为
os.File
,因此您的临时输出也应该是一个文件。

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"
)

func print() {
    fmt.Println("output")
}

func main() {
    // setting stdout to a file
    fname := filepath.Join(os.TempDir(), "stdout")
    fmt.Println("stdout is now set to", fname)
    old := os.Stdout // keep backup of the real stdout
    temp, _ := os.Create(fname) // create temp file
    os.Stdout = temp

    print()

    // back to normal state
    temp.Close()
    os.Stdout = old // restoring the real stdout

    // reading our temp stdout
    fmt.Println("previous output:")
    out, _ := ioutil.ReadFile(fname)
    fmt.Print(string(out))
}

我不推荐,因为这太多了,而且在 Go 中也不是很惯用。我建议将

io.Writer
传递给函数并将输出写入其中。这是做几乎同样事情的更好方法。

package main

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

func print(w io.Writer) {
    fmt.Fprintln(w, "output")
}

func main() {
    fmt.Println("print with byes.Buffer:")
    var b bytes.Buffer
    print(&b)
    fmt.Print(b.String())

    fmt.Println("print with os.Stdout:")
    print(os.Stdout)
}

3
投票

我认为整个想法根本不可取(竞争条件),但我想人们可能会以与您的示例类似/类比的方式弄乱 os.Stdout 。


1
投票

尽管上面列出的选项有效,但现代 Go 中有一种干净的方法,即使用 io.Pipe 和 io.Copy。

package main

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

// Your function
func some_function(w *io.PipeWriter) {
    defer w.Close()
    // Fill pipe writer
    fmt.Fprintln(w, "Hello World")
}

// main function
func main() {
    // create a pipe reader and writer
    pr, pw := io.Pipe()

    // pass writer to function
    go some_function(pw)

    // custom buffer to get standard output of function
    var b bytes.Buffer

    // create a multi writer that is a combination of
    // os.Stdout and variable byte buffer `b`
    mw := io.MultiWriter(os.Stdout, &b)

    // copies pipe reader content to standard output & custom buffer
    _, err := io.Copy(mw, pr)

    if err != nil {
        if err != io.EOF {
            panic(err)
        }
    }

    // use variable
    fmt.Println(b.String())
}

上面的程序是这样工作的:

  1. 创建一个提供读取器和写入器的管道。这意味着,如果你向管道写入器写入一些内容,将会被 go 复制到管道读取器
  2. 使用
    MultiWriter
    和自定义缓冲区
    os.Stdout
     创建一个 
    b
  3. some_function
    (作为 go 例程)会将字符串写入管道写入器
  4. io.Copy 然后会将内容从管道读取器复制到多写入器
  5. os.Stdout 将接收输出以及您的自定义缓冲区
    b
  6. 使用缓冲区
    b

io
包装内含所有电池,可与 io.Reader 和 io.Writer 配合使用。不需要使用
os
包,除非涉及到文件。

运行片段: https://goplay.tools/snippet/3NcLVNmbEDd

© www.soinside.com 2019 - 2024. All rights reserved.