【问题标题】:In Go, how do I capture stdout of a function into a string?在 Go 中,如何将函数的标准输出捕获到字符串中?
【发布时间】:2012-05-06 19:59:51
【问题描述】:

例如,在 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


    【解决方案1】:

    我同意你应该使用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)
    }
    

    【讨论】:

    • 这个更类似于Python的例子。 :)
    • 您真的希望/需要在写入管道的任何内容之前启动io.Copy,因为管道的缓冲能力存在限制。移动 goroutine 的启动是微不足道的,并且完全绕过了这个问题。例如:play.golang.org/p/PNqa5M8zo7
    • 也不要忽略来自os.Pipe 的错误,并且可能使用defer 来恢复os.Stdout,这样任何可能退出函数的添加代码都不需要担心它。这对于并发使用是非常不安全的。
    • 为什么代码比 Python 示例多那么多?不是想拖钓,真的很好奇为什么 Go 似乎需要更复杂的操作来完成这个看似简单的操作。
    • 我把你的逻辑放到了一个库中:github.com/PumpkinSeed/cage
    【解决方案2】:

    这个答案与之前的答案相似,但使用 io/ioutil 看起来更简洁。

    http://play.golang.org/p/fXpK0ZhXXf

    package main
    
    import (
      "fmt"
      "io/ioutil"
      "os"
    )
    
    func main() {
      rescueStdout := os.Stdout
      r, w, _ := os.Pipe()
      os.Stdout = w
    
      fmt.Println("Hello, playground") // this gets captured
    
      w.Close()
      out, _ := ioutil.ReadAll(r)
      os.Stdout = rescueStdout
    
      fmt.Printf("Captured: %s", out) // prints: Captured: Hello, playground
    }
    

    【讨论】:

    • 这与接受的答案相同。写入管道然后从同一个 goroutine 读回它是一个坏主意;特别是如果你暗示你的中间“块”可以是任何输出量的东西。不能,管道的缓冲能力有限。
    • 我在我的部分测试中使用它,因为我正在使用打印到标准输出的第三方包,我需要获取该输出。我不控制第三方代码,所以没有其他方法可以做到这一点。如果您想出另一种解决方案,我很高兴学习! ;-)
    • mattes,只需在接受的答案中使用 goroutine,但要早点启动它。否则,如果您遇到您无法控制的第三方代码决定转储大量输出的情况(可能失败,或者运行管道缓冲不同的不同操作系统),您将收到“致命错误:所有 goroutine 都处于休眠状态 - 死锁”(例如 play.golang.org/p/NtpJkCEXDX 失败)。
    • 这是我写的小插件的简单解决方案。谢谢!
    【解决方案3】:

    我不建议这样做,但您可以通过更改 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)
    }
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 你在想什么样的比赛条件?
      • os.Stdout 是一个全局变量。无条件/无保护设置和恢复是竞争条件的典型示例。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-10
      • 1970-01-01
      • 1970-01-01
      • 2019-04-10
      • 2011-05-15
      相关资源
      最近更新 更多