Rewrite multiple lines in the console

Viewed 49407

I know it is possible to consistently rewrite the last line displayed in the terminal with "\r", but I am having trouble figuring out if there is a way to go back and edit previous lines printed in the console.

What I would like to do is reprint multiple lines for a text-based RPG, however, a friend was also wondering about this for an application which had one line dedicated to a progress bar, and another describing the download.

i.e. the console would print:

Moving file: NameOfFile.txt  
Total Progress: [########              ] 40%

and then update appropriately (to both lines) as the program was running.

7 Answers

Like this:

#!/usr/bin/env python

import sys
import time
from collections import deque

queue = deque([], 3)
for t in range(20):
    time.sleep(0.5)
    s = "update %d" % t
    for _ in range(len(queue)):
        sys.stdout.write("\x1b[1A\x1b[2K") # move up cursor and delete whole line
    queue.append(s)
    for i in range(len(queue)):
        sys.stdout.write(queue[i] + "\n") # reprint the lines

I discovered this in the Jiri project, written in Go.

Even better: erase all lines after done:

#!/usr/bin/env python

import sys
import time
from collections import deque

queue = deque([], 3)
t = 0
while True:
    time.sleep(0.5)
    if t <= 20:
        s = "update %d" % t
        t += 1
    else:
        s = None
    for _ in range(len(queue)):
        sys.stdout.write("\x1b[1A\x1b[2K") # move up cursor and delete whole line
    if s != None:
        queue.append(s)
    else:
        queue.popleft()
    if len(queue) == 0:
        break
    for i in range(len(queue)):
        sys.stdout.write(queue[i] + "\n") # reprint the lines

Carriage return can be used to go to the beginning of line, and ANSI code ESC A ("\033[A") can bring you up a line. This works on Linux. It can work on Windows by using the colorama package to enable ANSI codes:

import time
import sys
import colorama

colorama.init()

print("Line 1")
time.sleep(1)
print("Line 2")
time.sleep(1)
print("Line 3 (no eol)", end="")
sys.stdout.flush()
time.sleep(1)
print("\rLine 3 the sequel")
time.sleep(1)
print("\033[ALine 3 the second sequel")
time.sleep(1)
print("\033[A\033[A\033[ALine 1 the sequel")
time.sleep(1)
print()  # skip two lines so that lines 2 and 3 don't get overwritten by the next console prompt
print()

Output:

> python3 multiline.py
Line 1 the sequel
Line 2
Line 3 the second sequel
>

Under the hood, colorama presumably enables Console Virtual Terminal Sequences using SetConsoleMode.

(also posted here: https://stackoverflow.com/a/64360937/461834)

You can try tqdm.

from time import sleep
from tqdm import tqdm
from tqdm import trange

files = [f'file_{i}' for i in range(10)]
desc_bar = tqdm(files, bar_format='{desc}')
prog_bar = trange(len(files), desc='Total Progress', ncols=50, ascii=' #',
                  bar_format='{desc}: [{bar}] {percentage:3.0f}%')

for f in desc_bar:
    desc_bar.set_description_str(f'Moving file: {f}')
    prog_bar.update(1)
    sleep(0.25)

enter image description here

There is also nested progress bars feature of tqdm

from tqdm.auto import trange
from time import sleep

for i in trange(4, desc='1st loop'):
    for k in trange(50, desc='2rd loop', leave=False):
        sleep(0.01)

enter image description here

Note that nested progress bars in tqdm have some Known Issues:

  • Consoles in general: require support for moving cursors up to the previous line. For example, IDLE, ConEmu and PyCharm (also here, here, and here) lack full support.
  • Windows: additionally may require the Python module colorama to ensure nested bars stay within their respective lines.

For nested progress bar in Python, Double Progress Bar in Python - Stack Overflow has more info.

I found simple solution with a "magic_char".

magic_char = '\033[F'
multi_line = 'First\nSecond\nThird'
ret_depth = magic_char * multi_line.count('\n')
print('{}{}'.format(ret_depth, multi_line), end='', flush = True)
Related