0

I have some output I'd like to write over and have a simple Python function that accomplishes this fine for me (in Terminal on OS X) but am unsure if I can rely on it in general:

import sys
import time

def print_over(s):
    print(s, end='\r')
    print("\033[F" * (s.count('\n')+1))
    sys.stdout.flush() 
    time.sleep(0.2)

I understand that there will be some contexts where this won't work, of course, but wonder

  • how widely I can expect it to work (e.g., is it just some quirk of OS X that allows this),
  • how to characterize where it will and won't work (e.g., is there some POSIX or other standard that guarantees that it will), and
  • whether I can detect from my code whether it will work.
orome
  • 45,163
  • 57
  • 202
  • 418

2 Answers2

1

Ignoring the discussion about portability to Microsoft Console API (which OP could explore), and just focusing on places where "ANSI-escapes" work:

This line is of particular interest, since it is the only escape sequence used:

print("\033[F" * (s.count('\n')+1))

That corresponds to the ECMA-48 control CPL, e.g., as in XTerm Control Sequences:

CSI Ps F  Cursor Preceding Line Ps Times (default = 1) (CPL).
  • OP asked "how widely I can expect it to work". It depends. It is implemented in xterm (and Terminal.app implements a fair chunk of that), but was not part of VT100 or VT220 (see documentation at http://vt100.net). It was added to xterm in 1996. So consider it limited to programs imitating xterm.

  • POSIX has nothing to say on the topic. X/Open Curses (not part of POSIX) is close—but CPL does not correspond to any of the terminfo capabilities. ECMA-48 is relevant, but there is no guarantee that any feature listed in ECMA-48 is implemented in any given terminal. Rather, it enumerates possibilities and prescribes their syntax. There is no guarantee likewise that any given feature of xterm is found in another program (see for example Comparing versions, by counting controls).

  • In principle, one might try using the cursor-position report (CPR control sequence) to see where the cursor is after using CPL, but even that is unreliable on some "xterm emulators".

By the way, the CPL control sequence accepts a repeat-parameter, so the print statement could be rewritten to use that (rather than repeating the control sequence).

If you want to be a little more portable, using the CUU (cursor-up) control works with VT100, and (like CPL) can be parameterized with a repeat-count. That is "\033[A":

CSI Ps A  Cursor Up Ps Times (default = 1) (CUU).
Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
0

Why not using curses? It works natively in Linux, OSX and now there is also a Windows implementation (as reported here).

The following example would be reliable on most platforms:

from curses import wrapper
import time

def print_over(scr, s):
    scr.clear()
    scr.addstr(5, 0, s)
    scr.refresh()

def main(scr):
    for i in range(10, 110, 10):
        print_over(scr,'Progress: %d %%'%i)
        time.sleep(1)

wrapper(main)

EDIT:

Here another example that does not clear the whole screen:

from curses import tparm, tigetstr, setupterm
import time

def tput(cmd, *args):
    print (tparm(tigetstr(cmd), *args), end='') # Emulates Unix tput

class window():
    def __init__(s, x, y, w, h):
        s.x, s.y, s.w, s.h = x, y, w, h # store window coordinates and size

    def __enter__(s):
        tput('sc') # saves current cursor position
        s.clear()  # clears the window
        return s

    def __exit__(s, exc_type, exc_val, exc_tb):
        tput('rc') # restores cursor position

    def clear(s, fill=' '):
        for i in range(s.h):
            tput('cup', s.y+i, s.x) # moves cursor to the leftmost column
            print (fill*s.w, end='')

    def print_over(s, msg, x, y):
        tput('cup', s.y+y, s.x+x)
        print (msg, end='')

setupterm()
with window(x=5, y=10, w=80, h=5) as w:
    for i in range(10, 110, 10):
        w.clear()
        w.print_over('Progress: %d %%'%i, 5, 2)
        time.sleep(1)

And here another one that overwrites just the last line:

from curses import tparm, tigetstr, setupterm
import time

def tput(cmd, *args):
    print (tparm(tigetstr(cmd), *args), end='') # Emulates Unix tput

setupterm()
for i in range(10, 110, 10):
    tput('el') # clear to end of line
    print (' Progress: %d %%'%i, end='\r')
    time.sleep(1)

Basically, the principle is always to use curses with tput commands to avoid any explicit escape characters.

Note that you might need to flush stdout or just launch the script with python -u.

Community
  • 1
  • 1
Giancarlo Sportelli
  • 1,219
  • 1
  • 17
  • 20