148

I have a growing log file for which I want to display only the last 15 lines. Here is what I know I can do:

tail -n 15 -F mylogfile.txt

As the log file is filled, tail appends the last lines to the display.

I am looking for a solution that only displays the last 15 lines and get rid of the lines before the last 15 after it has been updated. Would you have an idea?

5
  • 31
    Resize your terminal window to 15 lines.
    – Jonathan
    Commented Jan 19, 2012 at 19:48
  • 1
    That's a good one too. Commented Jan 20, 2012 at 9:08
  • 3
    @Jonathan genius! Commented Sep 19, 2015 at 0:00
  • 1
    Couldn't upvote jonathan's comment or it loses the flavor of having 15 upvotes
    – jdero
    Commented Aug 19, 2016 at 18:24
  • That's a good question. By today's terminology one would say you want a widget for you shell script to do something like that if you want to have it all in one script in one shell (tmux would be an option otherwise). github.com/charmbracelet/gum is building something like that, but their features are not on this level yet.
    – LiveWireBT
    Commented Feb 6, 2023 at 10:19

6 Answers 6

185

It might suffice to use watch:

$ watch tail -n 15 mylogfile.txt
3
  • 1
    Great! Thanks a lot. I also learn the 'watch' command in the process. Commented Jan 19, 2012 at 12:41
  • 2
    Be aware that this solution doesn’t work if the file grows more than 15 lines between two watch updates. You can use -n to change this interval.
    – bfontaine
    Commented Aug 5, 2016 at 10:00
  • This is perfect! And solution works perfectly well with files longer than 15 lines...
    – Akaisteph7
    Commented Jan 26, 2023 at 15:14
36

If you use watch, try the -n option to control the interval between each update.

Thus, the following would call tail every 2 seconds

$ watch -n 2 tail -n 15 mylogfile.txt

while this one polls it every 1 second

$ watch -n 1 tail -n 15 mylogfile.txt
16

You could stream the logfile running less and pressing SHIFT + F that will stream the file using less. $ less mylogfile.txt Then just press SHIFT + F and it will stream. I think it is convenient for monitoring log files that update.

2
  • 1
    That's a very nice one!
    – kaiser
    Commented Mar 10, 2016 at 15:21
  • Indeed, not what I was looking for but close. You should also be able to use less +F mylogfile.txt as the manual says it it is similar in behavior to tail -f. If you, dear comment reader haven't used less +G mylogfile.txt yet, then you now know how to immediately jump to the end of a log file. A useful and great time saver on old an poorly maintained systems.
    – LiveWireBT
    Commented Feb 6, 2023 at 10:14
8

Maybe you find the -d param handy.

man watch

-d Highlight the differences between successive updates. Option will read optional argument that changes highlight to be permanent, allowing to see what has changed at least once since first iteration.

1
0

In Solaris, AIX or HPUX or UNIX-like (including Linux) you can use scripts to monitoring logs or anything like that:

while true; 
    clear; 
    do date; 
    echo ;
    echo "MONITORING LOG IN "/path/to/file.log": "; 
    echo "Obs.: Last 20 lines of a logfile:
    echo ;
    tail -20 /path/to/file.log;
    echo ;
sleep 5; 
done
0

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 "$@"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .