3

I would like to position the cursor at a specific row/column and print a multi-line file/command that stays aligned to its first coordinate, so that

tput clear
tput cup 5 15
ping www.google.com

would output all subsequent lines in the 15th column. As it is, the first line prints correctly but the following lines are reset to the left. Is it possible using tput or any other method?

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
  • 1
    tput doesn't effect any kind of complex state change in the terminal; it just writes a few bytes to standard output. The terminal treats the bytes from tput cup 5 15 the same as it does a linefeed; as an instruction to move the cursor one time before continuing with the following bytes. – chepner Nov 17 '17 at 14:22

3 Answers3

3

One way to do it could be to set a tab stop at that position:

trap 'tabs -8' EXIT INT TERM # restore to default tab stops every 8 columns
                             # upon exit or signal

tput tbc # clear tab stops
tput cup 5 15
tput hts # set one and only tab stop
printf '\r' # move back to the beginning of the line
printf '\t%s\n' foo bar baz
ping www.google.com | paste /dev/null -

That does affect the behaviour of the terminal and could cause problems when suspended for instance.

Advantages over @Thor's cup based approach is that it sends less output (not really a concern unless you're on a 300 baud satellite link) and behaves more gracefully if some other process like syslog is also writing text to the terminal.

Another approach to makes sure each line starts at position 15, would be to prefix each line with \r$(tput cuf 15):

tput cup 5 15
ping www.google.com | PREFIX=$(tput cr; tput cuf 15) awk '
  {print ENVIRON["PREFIX"] $0}'

See also the csr capability to set a scrolling region.

If using zsh, see also its zcurses builtin

  • Both @thor answer and yours produce the result I was after. I was already commenting to ask how to implement the output of the ping command with your method when you edited the answer. As it is, it does seem a cleaner approach. Do you agree? By the way, what exactly does the tabs -8 do? – Radvansky Nov 17 '17 at 14:44
  • @Radvansky, as seen in the comment, it restores the default tab stops. You may also want to add that tabs -8 to a SIGINT/TERM trap to make sure the tabs are restored if that script is interrupted. It's one way in that it's less "clean": it affects the behaviour of the terminal. That can also cause problems if the script is suspended for instance. It would not work after stty -tabs (but people would only do that on terminals that don't support tabs in the first place anyway). – Stéphane Chazelas Nov 17 '17 at 14:52
3

Yes.

… without needing to post-process the output of the program in question, or have it change its behaviour because it thinks that it is talking to a pipe and not to a terminal character device (as some programs indeed do).

tput is not capable of this, because there are no terminfo capabilities for the requisite functionality. Moreover, this functionality is specific to a particular class of terminals, those that implement several control sequences from the DEC VT family of terminals.

But it is possible.

That is a small number of terminals. Emulating the DEC VTs and their control sequences is a popular thing for terminal emulators. But not all terminal emulators understand and implement the specific DEC VT control sequences that are necessary for this. Ones that do include:

Such terminals have the DEC VT notion of settable margins. Margins control scrolling and automatic margin wrap behaviour of normal output. The terminals also have the DEC VT notion of origin mode. This is a mode setting which, when set on, makes margins also control absolute cursor positioning done with the CUP and HVP control sequences. Finally, they implement all of the margins, including the newer ones provided by the later DEC VT models.

So when your program is started or continued:

  • Set the top and bottom margins by emitting the DECSTBM control sequence.
  • Enable the left and right margin mechanism by turning the DECLRMM mode on.
  • Set the left and right margins by emitting the DECSLRM control sequence.
  • Turn the DECOM mode on.
  • Home the cursor, just in case it was outwith the margins when you turned origin mode on and turning origin mode on does not automatically move the cursor.

and do the reverse to restore the screen to normal when your program exits, is suspended, or is terminated. You also need to keep the information about the terminal size held in the line discipline correct, so that programs that use cursor addressing mode know the size of the margin area rather than the size of the whole screen.

At first pass, that will be something like

printf '\e[%d;%dr\e[?69h\e[%d;%ds\e[?6h\e[H' 5 20 5 65
stty rows 15 columns 60
ping www.google.com
printf '\e[?6l\e[s\e[?69l\e[r'
stty rows 25 columns 80
although you should vastly improve the cleanup behaviour of that in the face of the script being terminated, as well as be more clever about restoring the rows and columns in the line discipline to the correct values (rather than making bad assumptions as I have done here for the sake of brevity).

For lesser functionality, these terminal emulators respect top and bottom margins, but do not have the left and right margin mechanism that you specifically want here:

  • urxvt
  • Konsole
  • dtterm
  • PuTTY

Note that margins have to be set independently for the primary and alternate screen buffers. So programs that switch to the alternate screen buffer when switching to cursor addressing mode will not be affected by this. That group includes programs like NeoVIM and less. Programs that do cursor addressing without switching to the alternate screen buffer will be, however. That latter group includes things like ZLE in the Z shell.

Further reading

JdeBP
  • 68,745
0

Short answer: No.

However, you can simulate the effect by emitting a tput cup before every new line, e.g.:

i=5
tput clear
tput cup $i 15
ping google.com | 
while read; do 
  tput cup $((++i)) 15
  echo "$REPLY"
done

Or as a single "command":

i=5; tput clear; tput cup $i 15; ping google.com | 
while read; do tput cup $((++i)) 15; echo "$REPLY"; done

Depending on how complicated your cursor manipulation is, you may want to implement it in ncurses. Another alternative might be Bash Simple Curses.

Thor
  • 17,182