tail

Show the last part of a file, or follow it live

Updated 2026-08-23

tail prints the last 10 lines of each file it is given, or of standard input. -n changes the count, -c counts bytes, and several files each get a ==> filename <== header that -q suppresses.

That much mirrors head. Two things do not.

A + in the count changes what it counts from. tail -n 5 means the last five lines; tail -n +5 means from line 5 to the end. It is the one flag people reliably get backwards, and tail -n +2 is the standard way to drop a header row before feeding a file to something else. -c +N does the same by byte offset, counting from 1 rather than 0.

-f does not exit. Instead of stopping at the current end of the file, tail -f blocks and prints new lines as they are appended, which is the usual way to watch a log in real time. -n0 -f skips the existing content and shows only what arrives from now on. Since the process never ends on its own, --pid exists to make it stop when some other process does.

Following has one failure that is worth understanding before you rely on it. tail -f follows the file it opened, identified by its inode, not the name. When logrotate renames app.log to app.log.1 and creates a fresh app.log, -f carries on watching the renamed file, which nothing is writing to any more. It does not error, and it does not exit; it simply goes quiet, and the quiet looks exactly like a service that has stopped logging. -F is shorthand for --follow=name --retry, which watches the name, notices that the file behind it has been replaced, and says so before continuing with the new one. For anything under logrotate, which on Debian is most of /var/log, -F is the flag you want.

Sample files used on this page

Every example below was run against these files. Recreate them to follow along.

report.txt 40 numbered lines, each 5 words

line 1 of the report
line 2 of the report
line 3 of the report

line 40 of the report

users.csv a header row plus 6 records

name,age,department
Alice,34,Engineering
Bob,29,Sales
Carol,41,Engineering
Dave,25,Marketing
Erin,38,Sales
Frank,31,Engineering

access.log 10 requests in the standard combined log format

203.0.113.5 - - [13/Aug/2026:09:12:01] "GET /index.html HTTP/1.1" 200 512
203.0.113.5 - - [13/Aug/2026:09:12:03] "GET /style.css HTTP/1.1" 200 231
198.51.100.7 - - [13/Aug/2026:09:14:22] "GET /index.html HTTP/1.1" 200 512
198.51.100.7 - - [13/Aug/2026:09:14:25] "GET /missing.html HTTP/1.1" 404 162
203.0.113.5 - - [13/Aug/2026:09:15:47] "GET /index.html HTTP/1.1" 200 512
192.0.2.44 - - [13/Aug/2026:09:16:03] "POST /login HTTP/1.1" 302 0
192.0.2.44 - - [13/Aug/2026:09:16:04] "GET /dashboard HTTP/1.1" 200 4021
198.51.100.7 - - [13/Aug/2026:09:18:51] "GET /index.html HTTP/1.1" 200 512
203.0.113.5 - - [13/Aug/2026:09:19:10] "GET /api/status HTTP/1.1" 500 89
192.0.2.44 - - [13/Aug/2026:09:20:33] "GET /dashboard HTTP/1.1" 200 4021

bytes.txt ten bytes, no trailing newline, so the -c examples have something exact to cut

abcdefghij

livelog.txt empty, and there so the -f examples have a file to open

0
50 outputs, collapsed by default

The last N lines

With no flags at all, tail prints 10 lines.

Show the last 10 lines of a file

tail report.txt

The default count, and the reason tail file is the first thing to type when a log has gone wrong.

Show output
line 31 of the report
line 32 of the report
line 33 of the report
line 34 of the report
line 35 of the report
line 36 of the report
line 37 of the report
line 38 of the report
line 39 of the report
line 40 of the report

Show the last 5 lines

tail -n 5 report.txt

-n takes the count. The portable spelling, and the one to write in a script.

Show output
line 36 of the report
line 37 of the report
line 38 of the report
line 39 of the report
line 40 of the report

Use the older shorthand for the same thing

tail -5 report.txt

-5 means -n 5. Obsolescent in POSIX terms, universally supported, and shorter at a prompt.

Show output
line 36 of the report
line 37 of the report
line 38 of the report
line 39 of the report
line 40 of the report

Write the count out in full

tail --lines=4 report.txt

--lines and --bytes are the long forms of -n and -c, worth the extra characters in a script someone else will read.

Show output
line 37 of the report
line 38 of the report
line 39 of the report
line 40 of the report

Show just the last line

tail -n1 report.txt

The single most common use: the last thing a file recorded.

Show output
line 40 of the report

Write a negative count and get the same answer

tail -n -5 report.txt

-n -5 and -n 5 mean the same thing to tail. Only a + changes the direction, which is covered below.

Show output
line 36 of the report
line 37 of the report
line 38 of the report
line 39 of the report
line 40 of the report

Read the last records of a CSV

tail -n 3 users.csv

The header is long gone by this point in the file, so what comes back is records only.

Show output
Dave,25,Marketing
Erin,38,Sales
Frank,31,Engineering

Take the end of a pipeline

sort access.log | tail -2

With no filename, tail reads standard input. After a sort this is how you see the largest values rather than the smallest.

Show output
203.0.113.5 - - [13/Aug/2026:09:15:47] "GET /index.html HTTP/1.1" 200 512
203.0.113.5 - - [13/Aug/2026:09:19:10] "GET /api/status HTTP/1.1" 500 89

Take the end of generated output

seq 100 | tail -4

Anything writing to standard output can be trimmed this way, with no temporary file involved.

Show output
97
98
99
100

Take the end of literal text

printf 'one\ntwo\nthree\nfour\n' | tail -2

Useful for checking what a pipeline does to a small sample before pointing it at a real file.

Show output
three
four

Ask for nothing and get nothing

tail -n 0 report.txt; echo "(nothing)"

-n 0 prints no lines. It matters mainly in combination with -f, where it means "only what happens from now on".

Show output
(nothing)

Write the last lines to a new file

tail -n3 report.txt > last3.txt && cat last3.txt

tail writes to standard output like anything else, so the shell's > does the work. The cat is only here to show the result.

Show output
line 38 of the report
line 39 of the report
line 40 of the report

Count what tail actually returned

tail -n2 report.txt | wc -l

A file shorter than the requested count returns fewer lines without complaint, so a script that depends on getting exactly N should check.

Show output
2

Counting from the start with +N

A + reverses what the number means. tail -n +5 is not "the last 5 lines" but "everything from line 5 onwards", which makes tail the standard way to skip a header.

Skip the header row of a CSV

tail -n +2 users.csv

From line 2 to the end, so the column names are dropped and every record is kept. The idiom to use before piping a CSV into anything that would choke on its header.

Show output
Alice,34,Engineering
Bob,29,Sales
Carol,41,Engineering
Dave,25,Marketing
Erin,38,Sales
Frank,31,Engineering

Start from an arbitrary line

tail -n +36 report.txt

Line 36 onwards. Unlike -n 5, this counts from the start, so it gives the same answer however long the file grows.

Show output
line 36 of the report
line 37 of the report
line 38 of the report
line 39 of the report
line 40 of the report

Use the file's length to pick the starting line

wc -l report.txt; tail -n +39 report.txt

wc -l says the file has 40 lines, so +39 gives the last two. The same result as -n 2, arrived at from the other direction.

Show output
40 report.txt
line 39 of the report
line 40 of the report

Reproduce cat with tail

tail -n +1 report.txt | head -2

+1 means "from the first line", so it prints the whole file. head -2 is only here to keep this example short.

Show output
line 1 of the report
line 2 of the report

Count the records after a header

tail -n +2 users.csv | wc -l

Skipping the header counts records rather than lines, which is what almost every CSV question actually wants: six here, from a file of seven lines.

Show output
6

Extract a column from the records only

tail -n +2 users.csv | cut -d, -f1

Dropping the header first means the column name does not turn up in the middle of the data.

Show output
Alice
Bob
Carol
Dave
Erin
Frank

Sort records without disturbing the header

tail -n +2 users.csv | sort -t, -k2 -n | head -2

Skip the header, sort the rest by the second field numerically, and take the two youngest. Sorting the file directly would place the header wherever name happens to sort.

Show output
Dave,25,Marketing
Bob,29,Sales

Several files at once

Given more than one file, tail labels each with a ==> filename <== header and separates them with a blank line.

Read the end of two files together

tail -n3 report.txt users.csv

The headers are what make the combined output readable, and they appear whenever there is more than one file.

Show output
==> report.txt <==
line 38 of the report
line 39 of the report
line 40 of the report

==> users.csv <==
Dave,25,Marketing
Erin,38,Sales
Frank,31,Engineering

Read the end of three files

tail -n2 report.txt users.csv access.log

tail -n2 /var/log/*.log is the everyday form of this, for seeing what several logs last recorded.

Show output
==> report.txt <==
line 39 of the report
line 40 of the report

==> users.csv <==
Erin,38,Sales
Frank,31,Engineering

==> access.log <==
203.0.113.5 - - [13/Aug/2026:09:19:10] "GET /api/status HTTP/1.1" 500 89
192.0.2.44 - - [13/Aug/2026:09:20:33] "GET /dashboard HTTP/1.1" 200 4021

Suppress the headers

tail -q -n2 report.txt users.csv

-q runs the results together with nothing between them, which suits output going to another command rather than to a person.

Show output
line 39 of the report
line 40 of the report
Erin,38,Sales
Frank,31,Engineering

Force a header for a single file

tail -v -n2 report.txt

-v prints the header even for one file, so a loop over filenames produces consistently labelled output.

Show output
==> report.txt <==
line 39 of the report
line 40 of the report

See what happens when one file is missing

tail -n2 report.txt nosuchfile.txt; echo "exit: $?"

tail reads what it can, reports what it cannot on standard error, and exits 1. A script checking the status sees the failure even though real output also arrived.

Show output
==> report.txt <==
line 39 of the report
line 40 of the report
tail: cannot open 'nosuchfile.txt' for reading: No such file or directory
exit: 1

Read a file that is not there at all

tail nosuchfile.txt; echo "exit: $?"

The same error and status, with nothing to print alongside it.

Show output
tail: cannot open 'nosuchfile.txt' for reading: No such file or directory
exit: 1

Bytes instead of lines

-c counts bytes and stops mid-line without hesitation, which suits binary files and fixed-width records rather than prose. On UTF-8 text it will happily cut a character in half.

Show the last N bytes

tail -c 20 report.txt

Exactly 20 bytes from the end, which lands mid-word because the final line is 20 characters plus a newline.

Show output
ne 40 of the report

Take the last few bytes

tail -c 3 bytes.txt; echo

Useful for checking how a file ends: whether it has a trailing newline, or which byte a truncated download stopped at.

Show output
hij

Write the byte count out in full

tail --bytes=10 report.txt

--bytes is the long form of -c, and reads better in a script than a bare number.

Show output
he report

Count bytes from the start instead

tail -c +866 report.txt

+N counts from the beginning of the file, and bytes are numbered from 1, so this starts at the 866th byte.

Show output
eport

Read the whole file by byte offset

tail -c +1 bytes.txt; echo

+1 is the first byte, so this prints everything. Byte offsets starting at 1 rather than 0 is the detail to remember here.

Show output
abcdefghij

Following a growing file

-f is what separates tail from head: instead of exiting at the current end of the file, it blocks and prints new lines as they arrive.

Follow a file in real time

tail -f livelog.txt

Blocks and prints new lines as they are appended, instead of exiting once it reaches the current end of the file. Verified with a real background writer: two lines appended after tail -f started both appeared in its output within a second, live.

Show output
first new line
second new line

Follow only new lines, skipping existing content

tail -n0 -f watch.log

-n0 starts from the current end of the file instead of printing the last 10 lines first, which is the shape you want when only what happens from now on is interesting.

Filter a live-followed log

tail -n0 -f watch.log | grep --line-buffered ERROR

grep --line-buffered is needed here, because grep normally buffers its output in blocks when piped, which would delay matching lines until the buffer filled. Verified live: an INFO line and a later INFO line were both filtered out, and only the ERROR line in between made it through, in real time.

Show output
ERROR: disk full

Stop following once a specific process exits

tail -f --pid=1234 pidlog.txt

--pid makes tail exit on its own once the given PID is no longer running, rather than following forever, which suits tailing a service's log for the duration of a script that also manages that service. Verified: tail exited by itself immediately after the watched process was killed, and did not pick up a line appended after that.

Watch what a follow prints with nothing writing to the file

timeout 1 tail -f livelog.txt; echo "exit: $?"

livelog.txt is empty and nothing appends to it, so tail -f prints nothing and keeps waiting. timeout ends it after a second, and 124 is timeout's way of saying it had to.

Show output
exit: 124

Show the existing tail before following

printf 'a\nb\nc\n' > livelog.txt && timeout 1 tail -f -n1 livelog.txt; echo "exit: $?"

-f prints the last lines first and only then waits, so a follow always starts with some context. -n1 limits that context to one line.

Show output
c
exit: 124

Skip the existing content entirely

timeout 1 tail -f -n0 report.txt; echo "exit: $?"

Against a 40-line file, -n0 -f prints nothing at all: it starts at the end and waits. This is the difference between seeing history and seeing only what happens next.

Show output
exit: 124

Change how often tail looks for new data

timeout 2 tail -s 5 -f livelog.txt; echo "exit: $?"

-s sets the sleep interval in seconds between checks. On Linux tail uses inotify and reacts immediately anyway, so this matters mainly on filesystems where inotify does not work, such as NFS.

Show output
exit: 124

Ask to retry a file that is not there

tail --retry -n1 nosuchfile.txt; echo "exit: $?"

--retry only means anything alongside -f, and tail says so rather than silently ignoring it.

Show output
tail: warning: --retry ignored; --retry is useful only when following
tail: cannot open 'nosuchfile.txt' for reading: No such file or directory
exit: 1

Log rotation, and why -f is not enough

logrotate renames the current log and creates a fresh file with the old name. -f follows the file it opened, and -F follows the name, which is the whole difference.

Plain -f keeps watching the old file after rotation

tail -f rotlog.txt

Verified: after rotlog.txt was renamed aside and a new file created with the same name, tail -f kept following the renamed file by its original inode. It printed the content from before the rotation and never saw the new file's content at all, while still running and reporting no error.

Show output
before rotation

-F notices the replacement and follows the new file

tail -F --sleep-interval=1 rotlog4.txt

Verified: -F detected that rotlog4.txt had been replaced, printed an explicit notice saying so, and picked up the new file's content. This is the correct choice for following anything that might be rotated out from under it, which on Debian is most of /var/log.

Show output
before rotation
tail: 'rotlog4.txt' has been replaced;  following new file
after rotation

Everyday patterns

Where tail turns up in real pipelines, usually pointed at something that is still being written.

Pull a slice out of the middle of a file

head -n15 report.txt | tail -n3

First 15 lines, then the last 3 of those, giving lines 13 to 15. head approaches the same file from the other end.

Show output
line 13 of the report
line 14 of the report
line 15 of the report

Read the most recent requests from a log

tail -n5 access.log

The last five entries, which for a log being written to is the part anyone actually wants.

Show output
192.0.2.44 - - [13/Aug/2026:09:16:03] "POST /login HTTP/1.1" 302 0
192.0.2.44 - - [13/Aug/2026:09:16:04] "GET /dashboard HTTP/1.1" 200 4021
198.51.100.7 - - [13/Aug/2026:09:18:51] "GET /index.html HTTP/1.1" 200 512
203.0.113.5 - - [13/Aug/2026:09:19:10] "GET /api/status HTTP/1.1" 500 89
192.0.2.44 - - [13/Aug/2026:09:20:33] "GET /dashboard HTTP/1.1" 200 4021

Pull one field out of the last entry

tail -n1 access.log | cut -d' ' -f1

The client address from the most recent request, which is the shape of a great many monitoring one-liners.

Show output
192.0.2.44

Extract the request line from recent entries

tail -n2 access.log | cut -d'"' -f2

Splitting on the quote character gives the request line itself, without the address and timestamp around it.

Show output
GET /api/status HTTP/1.1
GET /dashboard HTTP/1.1

Take the last of a filtered set

tail -n +2 users.csv | grep Engineering | tail -1

The same command does two different jobs in one pipeline: the first tail drops the header, the last takes one record from what survived the filter.

Show output
Frank,31,Engineering

Pick a single line by number

tail -n +2 users.csv | head -3 | tail -1

Skip the header, keep three records, take the last of them, giving the third record. sed -n '4p' users.csv does the same in one command.

Show output
Carol,41,Engineering

See only the last part of the default output

tail -n 10 report.txt | head -2

Chaining the two the other way round takes the first lines of the last ten, so this gives lines 31 and 32.

Show output
line 31 of the report
line 32 of the report

Take the last records without the header

tail -n +2 users.csv | tail -2

The header is dropped first, so the two records returned are records rather than whatever the last two lines happen to be.

Show output
Erin,38,Sales
Frank,31,Engineering