72

I want to output to stdout and have the output "overwrite" the previous output.

For example; if I output On 1/10, I want the next output On 2/10 to overwrite On 1/10. How can I do this?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189

4 Answers4

124

stdout is a stream (io.Writer). You cannot modify what was already written to it. What can be changed is how that stream's represented in case it is printed to a terminal. Note that there's no good reason to assume this scenario. For example, a user could redirect stdout to a pipe or to a file at will.

So the proper approach is to first check:

  • if the stdout is going to a terminal
  • what is that terminal's procedure to overwrite a line/screen

Both of the above are out of this question's scope, but let's assume that a terminal is our device. Then usually, printing:

fmt.Printf("\rOn %d/10", i)

will overwrite the previous line in the terminal. \r stands for carriage return, implemented by many terminals as moving the cursor to the beginning of the current line, hence providing the "overwrite line" facility.

As an example of "other" terminal with a differently supported 'overwriting', here is an example at the playground.

Will Barnwell
  • 4,049
  • 21
  • 34
zzzz
  • 87,403
  • 16
  • 175
  • 139
36

Use this solution if you want to rewrite multiple lines to the output. For instance, I made a decent Conway's "Game of Life" output using this method.

DISCLAIMER: this only works on ANSI Terminals, and besides using fmt this isn't a Go-specific answer either.

fmt.Printf("\033[0;0H")
// put your other fmt.Printf(...) here

Brief Explanation: this is an escape sequence which tells the ANSI terminal to move the cursor to a particular spot on the screen. The \033[ is the so-called escape sequence, and the 0;0H is the type of code telling the terminal move the cursor to row 0, column 0 of the terminal.

Source: https://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements

Frank Bryce
  • 8,076
  • 4
  • 38
  • 56
15

The solution for one string which will replace whole string

fmt.Printf("\033[2K\r%d", i)

For example, it correctly prints from 10 to 0:

for i:= 10; i>=0; i-- {
    fmt.Printf("\033[2K\r%d", i)
    time.Sleep(1 * time.Second)
}
fmt.Println()

which previous answers don't solve.

abonec
  • 1,379
  • 1
  • 14
  • 23
  • This is a great solution. If anyone is wondering what it actually does, take a look at [this](https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797). Basically, it's the equivalent of `ESC[2K` which means erase the entire line. – David Chalifoux Jul 24 '22 at 03:27
4

Found something worth sharing for problems like this.
Sharing for people who might be facing same problem in future

Check if output is being written to terminal. If so, use \r (carriage return) defined by terminal to move cursor to the beginning of line

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)
    }

}

Example code taken from

not-a-robot
  • 321
  • 1
  • 14