I am running code that produces a huge amount of output during more than an hour. During execution, I am logging my output to a file using tee
. However, the huge amount of output that is still sent to my terminal is a bit cumbersome, so I made a small function ShowLastLines
whereto I can pipe my output, that will show only the last 15 lines of output and updates in realtime.
This is the script:
function ShowLastLines {
numberoflines=15
i=1
while read var
do
echo $var
if [ $i -gt $numberoflines ]
then
tput sc && tput cuu $(( $numberoflines +1 )) && tput dl 1 && tput rc && tput cuu 1
fi
(( i++ ))
done
}
In practice I use it like so:
( long code ) 2>&1 | tee output.log | ShowLastLines
This works nicely as expected, however I'd like to add one more functionality, namely I want to pause the output (in ShowLastLines
, of course not in the original execution) when I hit spacebar (e.g. to investigate some detail I noticed), and let it continue when I hit spacebar again (where it will catch up with the execution output).
I tried a few things, but so far have not succeeded. I believe it might possibly be done by putting the standard input in non-blocking mode by using stty
(as in this thread):
stty -echo -icanon time 0 min 0
but I don't manage to combine it with my existing read
.
An important side remark: I am on an AFS cluster, so I cannot access the log file (written by tee
) until the calculation has finished due to how the synchronisation between the different machines is implemented (I know that otherwise it would have been easier to use a tail -f
on the log file).
Ps: the most useful implementation would be that, when the output is paused by hitting spacebar, it is possible to scroll up within the output to also show the lines before. Any suggestions are welcome, but I realise that this probably requires a totally different implementation that might add a lot of calculational overhead, so I am happy enough with a solution that gives a simple pause by spacebar.
This would probably be best done with an actual program using select()
to read the input from the pipe and the user at the same time.
But, in a Bash script, you could use read
with a small timeout to check if the user has hit a key:
buf() {
stty -echo < /dev/tty
while read line ; do
echo "$line"
if read -n1 -t0.0001 -u3 3</dev/tty ; then
echo paused.
read -n1 -u3 3</dev/tty
fi
done
stty echo < /dev/tty
}
read -n1
will not wait for a whole line, but will return immediately after a character has been received. We need to read
from /dev/tty
(stderr might also do), since we expect the piped input from stdin, as in somecmd | buf
.
The downside here is that if you stop the output for too long, the pipeline will stall and stop the process producing the output. You can work around this by using pv
(say, ...| pv -qB 64k |...
) in the pipeline to act as a buffer. Though viewing the output that was sent while the reading side was paused is somewhat difficult.
As mentioned in the comments, you could also use the terminal emulator's buffering to do this, too. That would have the same downside that stopping the output for too long would again stall the writing side of the pipe.