【问题标题】:How to capture stdout output but also show progress如何捕获标准输出并显示进度
【发布时间】:2020-08-24 15:10:19
【问题描述】:

我有一个名为 print() 的函数,它每 2 秒打印一次数字,这个函数在 goroutine 中运行。

我需要将它的标准输出打印传递给一个变量并打印它,但不是一次,直到它完成。
我需要有一个无限循环的扫描仪,它将扫描标准输出并打印它,一旦功能完成,扫描仪也会完成。

我尝试使用this answer,但它没有打印任何内容。
这是我试图做的:

package main

import (
    "bufio"
    "fmt"
    "os"
    "sync"
    "time"
)


func print() {

    for i := 0; i < 50; i++ {
        time.Sleep(2 * time.Second)
        fmt.Printf("hello number: %d\n", i)
    }
}

func main() {
    old := os.Stdout // keep backup of the real stdout

    defer func() { os.Stdout = old }()
    r, w, _ := os.Pipe()
    os.Stdout = w

    go print()


    var wg sync.WaitGroup

    c := make(chan struct{})
    wg.Add(1)


    defer wg.Done()
    for {
        <-c
        scanner := bufio.NewScanner(r)
        for scanner.Scan() {
            m := scanner.Text()
            fmt.Println("output: " + m)
        }

    }

    c <- struct{}{}

    wg.Wait()
    fmt.Println("DONE")

}  

我也尝试使用io.Copy 来读取这样的缓冲区,但它也不起作用:

package main

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


func print() {

    for i := 0; i < 50; i++ {
        time.Sleep(2 * time.Second)
        fmt.Printf("hello number: %d\n", i)
    }
}

// https://blog.kowalczyk.info/article/wOYk/advanced-command-execution-in-go-with-osexec.html
func main() {
    old := os.Stdout // keep backup of the real stdout

    defer func() { os.Stdout = old }()
    r, w, _ := os.Pipe()
    os.Stdout = w

    go print()

    fmt.Println("DONE 1")
    outC := make(chan string)

    for {

        var buf bytes.Buffer
        io.Copy(&buf, r)
        outC <- buf.String()

        out := <-outC
        fmt.Println("output: " + out)
    }

    // back to normal state
    w.Close()


    fmt.Println("DONE")

}

【问题讨论】:

  • 我不知道您要做什么,但是将 os.Stdout 分配给其他东西是错误的。如果您想在标准输出以外的地方打印,请使用Fprintln 而不是 Println。
  • 您的第一个 sn-p 几乎没有致命问题。例如。您尝试使用PipeScanner 捕获fmt.Printf - 这可能会起作用,但随后您将捕获的结果发​​送到fmt.Println(),它也会打印到Stdout,并且Stdout 已经重定向到Pipe(),所以结果不会打印到控制台(如您所料),而是再次打印到 Scanner 。还有其他问题,但应该先解决这个问题。
  • @maxim_ge 我把fmt.Println改成了fmt.Printf,还是不行。我试图将fmt.Printf 更改为fmt.Println,但它也不起作用:(
  • @Peter 我将fmt.Println 更改为fmt.Fprintf(w, "output: " + m),但它仍然没有打印任何内容。但是我需要从管道的标准输出中读取它,这就是问题所在。基本上我要做的是在函数仍在进行时读取函数的打印。
  • @E235 我会像这样重构你的代码play.golang.org/p/MfAGaC52o_P

标签: go pipe output stdout goroutine


【解决方案1】:

可以将print() 作为“黑盒”运行并捕获其输出,尽管它有点棘手并且不适用于 Go Playground。

package main

import (
    "bufio"
    "fmt"
    "os"
    "runtime"
    "time"
)


func print() {
    for i := 0; i < 50; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Printf("hello number: %d\n", i)
    }
}

func main() {

    var ttyName string
    if runtime.GOOS == "windows" {
    fmt.Println("*** Using `con`")
        ttyName = "con"
    } else {
    fmt.Println("*** Using `/dev/tty`")
        ttyName = "/dev/tty"
    }   

    f, err := os.OpenFile(ttyName, os.O_WRONLY, 0644)
    if err != nil {
        panic(err)
    }
    defer f.Close()

    r, w, _ := os.Pipe()
    oldStdout := os.Stdout
    os.Stdout = w
    defer func() { 
        os.Stdout = oldStdout
        fmt.Println("*** DONE")
    }()

    fmt.Fprintln(f, "*** Stdout redirected")

    go func(){
       print()
       w.Close()
       r.Close()          
    }()

    c := make(chan struct{})
    go func(){c <- struct{}{}}()
    defer close(c)

    <-c
    scanner := bufio.NewScanner(r)
    for scanner.Scan() {
        m := scanner.Text()
        fmt.Fprintln(f, "output: " + m)
    }
}

【讨论】:

  • 太棒了!它有效:) 谢谢!感谢您理解使用print() 作为黑盒的重要性,这对我很重要。仅供参考,它不适用于 Windows,只能在 Linux 上运行,但现在对我来说已经足够了。接下来我需要做的是查看当print() 已经完成时如何捕获打印但我会自己尝试,如果我有问题我会提出一个新问题
  • 我注意到当我删除time.Sleep(100 * time.Millisecond)时它不会打印它,是否可以让扫描仪等到print()功能完成?
  • @E235 实际上你必须等到扫描仪完成阅读。最好的方法是以某种方式刷新标准输出,但我不知道该怎么做。作为一种变通方法,您可以在print() 之后立即添加对time.Sleep() 的调用。这会给扫描仪一些时间
猜你喜欢
  • 1970-01-01
  • 2010-10-29
  • 2010-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-17
相关资源
最近更新 更多