4
votes

In my python program I want to print progress over a very long loop. I want to output specific information like percent complete etc..., but I don't want all this output to take up the whole screen.

Ideally, I want to to print a progress line. Something like

train 53/56...x6 │ loss:1.356 │ miou:0.276 │ rate=3.13 Hz, eta=0:00:01, total=0:00:17, wall=19:48 EST

Then, when the next line prints, I want to simply overwrite this line.

Currently I can do this by simply printing carriage return '\r' before I print my message. This returns the cursor to the beginning of the line and then overwrites the line. Exactly what I want.

The issue is when the terminal is too small for the entire line to fit, the line wraps around and the carriage return brings me to the beginning of the wrapped line, not the absolute beginning of the line.

Is there a way that I can bring the cursor all the way back to the beginning of the correct line?

1

1 Answers

5
votes

You can use ANSI escape sequences for cursor movement, most notably:

  • Position the Cursor: \033[<L>;<C>H, or \033[<L>;<C>f puts the cursor at line L and column C.
  • Move the cursor up N lines: \033[<N>A
  • Move the cursor down N lines: \033[<N>B
  • Move the cursor forward N columns: \033[<N>C
  • Move the cursor backward N columns: \033[<N>D
  • Save cursor position: \033[s
  • Restore cursor position: \033[u

Cursor position save/restore seem ideal for you case, but unfortunately these two codes are not honored by many terminal emulators.

They work in xterm and xfce4-terminal though (except when in the last line of terminal / scrolling output, as noted by @ThomasDickey in comments). Try:

echo -e "\033[s" {1..100} "\033[u" "Overwrite"

For other terminal emulators, you can try your luck with \033[<N>A to move cursor up for the required number of lines, and then move to column 0.

If you know the length of your line, you can calculate how many rows does it span when (and if wrapped) with (bash example, note the usage of COLUMNS environment variable):

line='...'
len=${#line}
rows=$((len / COLUMNS))

and then move up with:

printf "\033[%dA" "$rows"

In Python, you could use it like:

print("\033[s", "123"*100, "\033[u", "Overwrite", sep='')
print("\033[%dA" % 3, "Overwrite", sep='')

Or, abstract all this with something like curses.


Python solution

Based on the Move the cursor up N lines ANSI escape sequence (that should work in most terminal emulators), and a cross-Python compatible code for terminal width detection (in Python3 you can use shutil.get_terminal_size), here's a proof-of-concept demo that works with scrolling output, adapts to line length and changing terminal width:

#!/usr/bin/env python
from __future__ import print_function
import os
import time

cnt = 0
while True:
    with os.popen('stty size', 'r') as stty:
        rows, columns = stty.read().split()

    line = "Run: {}, Columns: {}, Filler: {}".format(cnt, columns, "***"*100)
    print(line)
    print("\033[{}A".format(len(line) // int(columns) + 1), end='')

    time.sleep(1)
    cnt += 1