Reputation: 5248
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?
Upvotes: 4
Views: 1344
Reputation: 18697
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
.
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
Upvotes: 6