Old question, but I've decided to write myself a bash function that does exactly that. Pasting the script here for those who want it. "ntail
" preserves the last N lines and has a timeout before updating the screen to minimize flickering effects when stdout updates too often.
You can try it out with the following example command, for which the screen updates should preserve the "date", but render a scrolling effect on the stdout of the for loop:
date; for i in $(seq 1 2000); do echo $i; sleep 0.03; done | ntail 10
#!/bin/bash
# Display last N lines of input like tail, but cleaning the screen before every update.
# Example: date; for i in $(seq 1 2000); do echo $i; sleep 0.03; done | ntail 10
function ntail {
# default to 10 lines of tail output
NUM_LINES=${1:-10}
# gets the current time in milliseconds
function mstime() {
date +%s%3N
}
LAST_UPDATE=$(mstime) # last time the screen was updated
NEEDS_REFRESH=false # whether to refresh the screen
SCREEN_BUFFER_SIZE=0 # number of lines on the screen
while IFS= read -r NEW_LINE; do
# concatenate new the new line to the buffer
TAIL_BUFFER="$TAIL_BUFFER$NEW_LINE"$'\n'
# if last update is greater than 100ms, refresh screen
if [ $(($(mstime) - LAST_UPDATE)) -gt 100 ]; then
NEEDS_REFRESH=true
fi
# refresh screen if needed
if [ "$NEEDS_REFRESH" = true ]; then
# reduce buffer size to last NUM_LINES lines
TAIL_BUFFER=$(echo "$TAIL_BUFFER" | tail -n "$NUM_LINES")$'\n'
# clear the last SCREEN_BUFFER_SIZE lines, preserving the stdout above that
for _ in $(seq 1 "$SCREEN_BUFFER_SIZE"); do
printf "\033[1A\033[2K"
done
# print the new buffer
printf "%s" "$TAIL_BUFFER"
SCREEN_BUFFER_SIZE=$(echo "$TAIL_BUFFER" | wc -l)
SCREEN_BUFFER_SIZE=$((SCREEN_BUFFER_SIZE - 1))
LAST_UPDATE=$(mstime)
NEEDS_REFRESH=false
fi
done < /dev/stdin
}
ntail "$@"