views:

377

answers:

3

I'm writing a filter (in a pipe destined for a terminal output) that sometimes needs to "overwrite" a line that has just occurred. It works by passing stdin to stdout character-by-character until a \n is reached, and then invoking special behaviour. My problem regards how to return to the beginning of the line.

The first thing I thought of was using a \r or the ANSI sequence \033[1G. However, if the line was long enough to have wrapped on the terminal (and hence caused it to scroll), these will only move the cursor back to the current physical line.

My second idea was to track the length of the line (number of characters passed since previous \n), and then echo \b that many times. However, that goes wrong if the line contained control characters or escape sequences (and possibly Unicode?).

Short of searching for all special sequences and using this to adjust my character count, is there a simple way to achieve this?

A: 

You can query terminal dimensions with a simple ioctl:

#include <sys/types.h>
#include <sys/ioctl.h>

// ...

struct winsize ws;
ioctl(1, TIOCGWINSZ, &ws);

// ws.ws_col, ws.ws_row should now contain terminal dimensions

This way you can prevent printing anything beyond the end of line and simply use the \r method.

laalto
Thanks for the suggestion! Unfortunately there are a couple of reasons why I don't think that will work for me:* I would still need to track special-case sequences to know how close to the edge I am* I may not be the last thing in the pipelineFor context; my filter is pattern-matching, and "re-writing" matching strings surrounded in colour escape sequences. I can't use `fgets()` and operate on whole strings at a time, as some of my input will produce "dynamic" output (e.g. an incrementing progress status) that doesn't output `\n` for a while.
Oli Charlesworth
A: 
$ cat >test.sh <<'EOF'
> #!/bin/sh
> tput sc
> echo 'Here is a really long multi-line string: .............................................................................................'
> tput rc
> echo 'I went back and overwrote some stuff!!!!'
> echo
> EOF
$ sh test.sh
I went back and overwrote some stuff!!!! .......................................
......................................................

Look for the save_cursor and restore_cursor string capabilities in the terminfo database.

ephemient
My understanding is that rc and sc save the *physical* location of the cursor (equivalent to \033[s and \033[u). If the entire content of the terminal scrolls due to the line-wrap, the beginning of the string is no longer at the same physical position. (At least, this is the behaviour I observe in xterm.)
Oli Charlesworth
Ah, that seems to happen in my terminal too. Aww...
ephemient
+1  A: 

Even if there were a "magic sequence" that when written to a console would reliably erase the last written line, you would STILL get the line and the sequence on the output (though hidden on a console). Think what would happen if somebody wrote the output to a file, or passed it down the pipe to other filters? Would they know how to handle such input? And don't tell me you rule out the possibility of writing somewhere else than directly to a console. Sooner or later, somebody WILL want to redirect the output - maybe even you!

The Right Way to do this is to buffer each line in memory as it is processed, and then decide whether to output it or not. There's really no way around this.

stormsoul
Indeed, that was my original solution. The only place that breaks is for stuff such as a progress percentage that increments over time (overwriting itself with \b), where there is no \n for several seconds or minutes, so fgets() and so on block for a considerable period. Incidentally, I have discounted the possibility that my filter is not the last thing in the pipeline (despite what I said in an earlier comment), as there's no reason to use it other than as a tool for prettifying the console output!
Oli Charlesworth
A quick comment about being the last item in the pipe: can't you make sure that stdout goes to a terminal, as `ls` does?
Jeff Kelley