在Go语言中,如何将函数的标准输出捕获到一个字符串中?

huangapple go评论91阅读模式
英文:

In Go, how do I capture stdout of a function into a string?

问题

在Go语言中,可以通过以下方式实现类似的功能:

package main

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

func main() {
	realout := os.Stdout
	var buf bytes.Buffer
	os.Stdout = &buf

	someFunction() // 将输出捕获到buf中

	result := buf.String()
	os.Stdout = realout

	fmt.Println(result)
}

func someFunction() {
	fmt.Println("Hello, World!")
}

在Go语言中,可以通过重定向os.Stdout到一个bytes.Buffer来捕获输出。然后,可以通过buf.String()获取捕获的输出结果。最后,将os.Stdout恢复为原来的值。

英文:

In Python, for example, I can do the following:

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

Can you do this in Go?

答案1

得分: 78

我同意你应该使用fmt.Fprint函数,如果你能够管理的话。然而,如果你不能控制输出的代码,你可能没有这个选项。

Mostafa的答案是有效的,但是如果你想不使用临时文件来完成,你可以使用os.Pipe。下面是一个示例,它与Mostafa的代码等效,并受到Go的testing包的启发。

package main

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

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

func main() {
    old := os.Stdout // 保留真实的stdout备份
    r, w, _ := os.Pipe()
    os.Stdout = w

    print()

    outC := make(chan string)
    // 在单独的goroutine中复制输出,以防止打印阻塞无限期
    go func() {
        var buf bytes.Buffer
        io.Copy(&buf, r)
        outC <- buf.String()
    }()

    // 恢复到正常状态
    w.Close()
    os.Stdout = old // 恢复真实的stdout
    out := <-outC

    // 读取我们的临时stdout
    fmt.Println("previous output:")
    fmt.Print(out)
}
英文:

I agree you should use the fmt.Fprint functions if you can manage it. However, if you don't control the code whose output you're capturing, you may not have that option.

Mostafa's answer works, but if you want to do it without a temporary file you can use os.Pipe. Here's an example that's equivalent to Mostafa's with some code inspired by Go's testing package.

package main

import (
    &quot;bytes&quot;
    &quot;fmt&quot;
    &quot;io&quot;
    &quot;os&quot;
)

func print() {
    fmt.Println(&quot;output&quot;)
}

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&#39;t block indefinitely
    go func() {
        var buf bytes.Buffer
        io.Copy(&amp;buf, r)
        outC &lt;- buf.String()
    }()

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

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

答案2

得分: 27

这个答案与之前的答案类似,但是通过使用io/ioutil来使代码看起来更简洁。

package main

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

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

  fmt.Println("Hello, playground") // 这个被捕获了

  w.Close()
  out, _ := ioutil.ReadAll(r)
  os.Stdout = rescueStdout
  
  fmt.Printf("Captured: %s", out) // 输出: Captured: Hello, playground
}
英文:

This answer is similar to the previous ones but looks cleaner by using io/ioutil.

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

package main

import (
  &quot;fmt&quot;
  &quot;io/ioutil&quot;
  &quot;os&quot;
)

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

  fmt.Println(&quot;Hello, playground&quot;) // this gets captured

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

答案3

得分: 16

我不推荐这样做,但你可以通过修改os.Stdout来实现。由于这个变量的类型是os.File,所以你的临时输出也应该是一个文件。

package main

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

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

func main() {
    // 将stdout设置为文件
    fname := filepath.Join(os.TempDir(), "stdout")
    fmt.Println("stdout现在设置为", fname)
    old := os.Stdout // 保留真实stdout的备份
    temp, _ := os.Create(fname) // 创建临时文件
    os.Stdout = temp

    print()

    // 恢复到正常状态
    temp.Close()
    os.Stdout = old // 恢复真实stdout

    // 读取我们的临时stdout
    fmt.Println("之前的输出:")
    out, _ := ioutil.ReadFile(fname)
    fmt.Print(string(out))
}

我不推荐这样做,因为这样做太过于hack,并且在Go语言中不是很惯用的做法。我建议将io.Writer传递给函数,并将输出写入其中。这是几乎相同的操作的更好方式。

package main

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

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

func main() {
    fmt.Println("使用bytes.Buffer打印:")
    var b bytes.Buffer
    print(&b)
    fmt.Print(b.String())

    fmt.Println("使用os.Stdout打印:")
    print(os.Stdout)
}
英文:

I don't recommend this, but you can achieve it with altering os.Stdout. Since this variable is of type os.File, your temporary output should also be a file.

package main

import (
    &quot;fmt&quot;
    &quot;io/ioutil&quot;
    &quot;os&quot;
    &quot;path/filepath&quot;
)

func print() {
    fmt.Println(&quot;output&quot;)
}

func main() {
    // setting stdout to a file
    fname := filepath.Join(os.TempDir(), &quot;stdout&quot;)
    fmt.Println(&quot;stdout is now set to&quot;, 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(&quot;previous output:&quot;)
    out, _ := ioutil.ReadFile(fname)
    fmt.Print(string(out))
}

I don't recommend because this is too much hacking, and not very idiomatic in Go. I suggest passing an io.Writer to the functions and writing outputs to that. This is the better way to do almost the same thing.

package main

import (
    &quot;bytes&quot;
    &quot;fmt&quot;
    &quot;io&quot;
    &quot;os&quot;
)

func print(w io.Writer) {
    fmt.Fprintln(w, &quot;output&quot;)
}

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

    fmt.Println(&quot;print with os.Stdout:&quot;)
    print(os.Stdout)
}

答案4

得分: 3

我认为整个想法都不可取(竞态条件),但我猜想可以以类似于你的示例的方式来操纵os.Stdout。

英文:

I think the whole idea is not advisable (race condition) at all, but I guess one can mess with os.Stdout in a way similar/analogical to your example.

答案5

得分: 1

尽管上面列出的选项可行,但在现代Go中有一种简洁的方法,可以利用io.Pipe和io.Copy。

package main

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

// 你的函数
func some_function(w *io.PipeWriter) {
	defer w.Close()
	// 填充管道写入器
	fmt.Fprintln(w, "Hello World")
}

// 主函数
func main() {
	// 创建一个管道读取器和写入器
	pr, pw := io.Pipe()

	// 将写入器传递给函数
	go some_function(pw)

	// 创建一个多写入器,它是os.Stdout和变量字节缓冲区`b`的组合
	mw := io.MultiWriter(os.Stdout, &b)

    // 将管道读取器的内容复制到标准输出和自定义缓冲区
	_, err := io.Copy(mw, pr)

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

	// 使用缓冲区
	fmt.Println(b.String())
}

上面的程序工作方式如下:

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

io包提供了与io.Reader和io.Writer一起使用的所有功能。除非涉及文件,否则不需要使用os包。

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

英文:

Even though the options listed above works, there is a clean approach in modern Go, that makes use of io.Pipe and io.Copy.

package main

import (
	&quot;bytes&quot;
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;os&quot;
)

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

// 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, &amp;b)

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

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

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

The above program works this way:

  1. Create a pipe that gives a reader and writer. It means, if you write something into pipe writer, will be copied to pipe reader by go
  2. Create a MultiWriter with os.Stdout and custom buffer b
  3. some_function(as a go-routine) will write a string into pipe writer
  4. io.Copy will then copy content from pipe reader into multi-writer
  5. os.Stdout will receive the output as well as your custom buffer b
  6. Use buffer b

io package comes with all batteries included to work with io.Reader and io.Writer. No need to use os package, unless files are involved.

Running snippet:
https://goplay.tools/snippet/3NcLVNmbEDd

huangapple
  • 本文由 发表于 2012年5月7日 03:59:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/10473800.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定