我想输出到stdout并让输出“覆盖”前一个输出。
例如;如果我输出On 1/10
,我希望下一个输出On 2/10
覆盖On 1/10
。我怎么能这样做?
答案 0 :(得分:75)
stdout
是一个流(io.Writer
)。您无法修改已写入的内容。 可以更改的内容是该流在打印到终端时的表示方式。请注意,没有充分的理由假设这种情况。例如,用户可以随意将stdout重定向到管道或文件。
所以正确的方法是首先检查:
以上两个都不属于这个问题的范围,但我们假设终端是我们的设备。然后通常打印:
fmt.Printf("\rOn %d/10", i)
将覆盖终端中的上一行。 \r
代表carriage return
,由许多终端实现为将光标移动到当前行的开头,因此提供了“覆盖行”功能。
作为具有不同支持“覆盖”的“其他”终端的示例,以下是playground的示例。
答案 1 :(得分:22)
如果要将多行重写为输出,请使用此解决方案。例如,我使用这种方法做了一个不错的Conway's "Game of Life"输出。
免责声明:这仅适用于ANSI终端,除了使用fmt
之外,这也不是Go特定的答案。
fmt.Printf("\033[0;0H")
// put your other fmt.Printf(...) here
简要说明:这是一个转义序列,它告诉ANSI终端将光标移动到屏幕上的特定位置。 \033[
是所谓的转义序列,0;0H
是代码类型,告诉终端将光标移动到终端的第0行第0行。
来源:https://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
答案 2 :(得分:3)
将替换整个字符串的一个字符串的解决方案
fmt.Printf("\033[2K\r%d", i)
例如,它可以正确打印10到0:
for i:= 10; i>=0; i-- {
fmt.Printf("\033[2K\r%d", i)
time.Sleep(1 * time.Second)
}
fmt.Println()
以前的答案不能解决。
答案 3 :(得分:0)
发现了一些值得分享的类似问题。
为将来可能遇到相同问题的人
检查输出是否正在写入端子。如果是这样,请使用终端定义的\ r(回车)将光标移至行首
package main
import (
"flag"
"fmt"
"os"
"time"
)
var spinChars = `|/-\`
type Spinner struct {
message string
i int
}
func NewSpinner(message string) *Spinner {
return &Spinner{message: message}
}
func (s *Spinner) Tick() {
fmt.Printf("%s %c \r", s.message, spinChars[s.i])
s.i = (s.i + 1) % len(spinChars)
}
func isTTY() bool {
fi, err := os.Stdout.Stat()
if err != nil {
return false
}
return fi.Mode()&os.ModeCharDevice != 0
}
func main() {
flag.Parse()
s := NewSpinner("working...")
isTTY := isTTY()
for i := 0; i < 100; i++ {
if isTTY {
s.Tick()
}
time.Sleep(100 * time.Millisecond)
}
}