4

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?

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
Erotemic
  • 4,806
  • 4
  • 39
  • 80

1 Answers1

6

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
randomir
  • 17,989
  • 1
  • 40
  • 55
  • Cursor save/restore won't help with the scenario asked about, if the output started at the bottom of the screen, because the line that you'd like to go to has moved. (Also, using hardcoded sequences is the problem **you** had with the cursor save/restore). – Thomas Dickey Nov 08 '17 at 01:57
  • That's true, fixed that in Python version. – randomir Nov 08 '17 at 02:22
  • @ThomasDickey, sorry, I missed your comment edit - what do you mean with hardcoded sequences not working? – randomir Nov 08 '17 at 02:34
  • If you remove the time.sleep(), it seems like you may run into a race condition causing you get the wrong terminal size and causing the screen to fill with weird output. Would using curses work around this issue? – Erotemic Nov 08 '17 at 03:43
  • Yes, if you remove `sleep`, the probability increases you'll sample the wrong terminal size during resize. Don't think curses could help you with that. But terminal resize shouldn't be that frequent, and OTOH, why would you need progress update every millisecond, or so? – randomir Nov 08 '17 at 03:54
  • xterm supports two different sequences for saving/restoring the cursor position, most terminals that imitate xterm support only one. The terminal description would list a workable one... – Thomas Dickey Nov 08 '17 at 09:00