Viewing files
tail
Print the last lines of files, or follow a file as it grows with -f.
tail [OPTION...] [FILE...]
tail prints the end of each file, 10 lines by default. On a regular file it seeks from the
end instead of reading everything, so it is just as fast on a huge log as on a small one.
Two forms do most of the work. tail -n +K turns the meaning around and prints from line K to
the end, which is how you skip a CSV header. tail -f keeps the file open and prints new lines
as they are appended, which is how most people watch a log live; tail -F does the same but
survives log rotation by reopening the file when it is replaced.
To see the start of a file use head; to scroll around a large one use less, whose
F key also follows a growing file.
Examples
Show the last 10 lines
tail logs/app.log
2026-03-14 08:03:45 WARN failed login for user=alice from 198.51.100.7 2026-03-14 08:03:46 WARN failed login for user=alice from 198.51.100.7 2026-03-14 08:05:09 INFO user lookup id=7 not found 2026-03-14 08:11:47 WARN permission denied: DELETE /api/users/3 2026-03-14 08:16:40 ERROR health check failed: database timeout after 5000ms 2026-03-14 08:16:50 INFO health check recovered 2026-03-14 08:20:12 WARN blocked request for /.env from 198.51.100.23 2026-03-14 08:25:00 INFO user id=2 updated by api 2026-03-14 08:30:00 ERROR disk usage on /var at 91% 2026-03-14 08:30:05 INFO cleanup job removed 42 temp files
The log has 12 lines, so the first two (server start and database connection) are left out.
Choose how many lines
tail -n 3 access.log
198.51.100.23 - - [14/Mar/2026:08:20:12 +0000] "GET /.env HTTP/1.1" 404 58 "-" "Mozilla/5.0 (compatible; scanner/1.0)" 192.0.2.10 - - [14/Mar/2026:08:22:45 +0000] "GET /contact HTTP/1.1" 200 2875 "https://example.com/about" "Mozilla/5.0 (X11; Linux x86_64)" 203.0.113.42 - - [14/Mar/2026:08:25:00 +0000] "PUT /api/users/2 HTTP/1.1" 200 221 "-" "python-requests/2.31"
-n 3 prints the three most recent requests.
Skip a header line
tail -n +2 data/users.csv | head -n 3
1,Alice Moreau,alice@example.com,engineering,2021-04-12 2,Bob Okafor,bob@example.com,sales,2019-11-03 3,Chen Wei,chen@example.com,engineering,2023-01-20
+2 means "start at line 2", so the CSV header is dropped and only data rows remain. The
head just shortens the output here.
The last bytes of a file
tail -c 12 notes.txt
e on web02.
-c 12 prints the final 12 bytes: e on web02. is 11 characters, and the newline that ends
the file is the twelfth.
The end of several files
tail -n 1 logs/app.log logs/error.log
==> logs/app.log <== 2026-03-14 08:30:05 INFO cleanup job removed 42 temp files ==> logs/error.log <== [2026-03-14 08:30:00] disk usage on /var at 91%
Each file gets a ==> name <== header; use -q to drop the headers.
Follow a file as it grows
(sleep 1; echo 'line added 1s later' >> logs/app.log) &
timeout 3 tail -n 2 -f logs/app.log
echo "tail stopped with status $?"
2026-03-14 08:30:00 ERROR disk usage on /var at 91% 2026-03-14 08:30:05 INFO cleanup job removed 42 temp files line added 1s later tail stopped with status 124
-f prints the last 2 lines, then waits and prints each new line as another process appends
it. tail -f never exits on its own; in a terminal you press Ctrl-C. Here timeout stops it
after 3 seconds, hence status 124.
Keep following across log rotation
tail -F /var/log/nginx/access.log
Not run in the sandbox: rename events inside the build sandbox's container filesystem are not delivered reliably, so the output would be misleading. No output is shown because none was captured.
When logrotate renames access.log and nginx starts a new one, -F notices, prints a
notice such as '/var/log/nginx/access.log' has been replaced; following new file on
standard error, and carries on with the new file. Plain -f would keep reading the renamed
old file and show nothing new.
Stop following when a process exits
echo 'job started' > job.log
(sleep 2; echo 'job done' >> job.log) &
tail -f --pid=$! job.log
echo "tail returned after the job finished"
job started job done tail returned after the job finished
--pid makes tail -f exit once the given process has ended, so a script can stream a
job's log and continue when the job is over, without timeout or Ctrl-C.
The last few matches in a log
grep WARN logs/app.log | tail -n 2
2026-03-14 08:11:47 WARN permission denied: DELETE /api/users/3 2026-03-14 08:20:12 WARN blocked request for /.env from 198.51.100.23
Filter first, then keep the end of the filtered stream to get the two most recent warnings.
A file that does not exist
tail -n 1 nope.log
tail: cannot open 'nope.log' for reading: No such file or directory
tail prints an error and exits with status 1.
Common pitfalls
tail -fon a log that gets rotated (renamed and recreated) silently goes quiet: it keeps following the old file. Usetail -F(same as--follow=name --retry).tail -n +2means from line 2, not the last 2 lines. Dropping the plus sign by accident prints a different thing entirely.tail -f file | grep xcan appear to show nothing for a while becausegrepbuffers its output when writing to a pipe. Addgrep --line-bufferedwhen another command reads grep's output.
GNU vs BSD and macOS
The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).
--pid,-s/--sleep-intervalwith fractions, and long options are GNU. BSD/macOStailsupports-f,-F,-n,-cand+N, and has-rto print a file in reverse (GNU usestacfor that).
Options worth knowing
A selection, described in our own words. For the full list, see tail(1) or tail --help.
| Option | What it does |
|---|---|
-n, --lines [+]NUM | Print the last NUM lines, or from line NUM on with a leading plus. |
-c, --bytes [+]NUM | Print the last NUM bytes, or from byte NUM on with a leading plus. |
-f, --follow[=name|descriptor] | Keep printing data appended to the file; by default follows the open file, not the name. |
-F | Follow by name and retry: keeps working when the file is rotated or recreated. |
--retry | Keep trying to open a file that is missing or becomes inaccessible. |
--pid PID | With -f, exit after process PID has ended. |
-s, --sleep-interval SECONDS | With -f, how long to wait between checks when polling. |
-q, --quiet | Never print file name headers. |
-v, --verbose | Always print file name headers. |
-z, --zero-terminated | Lines end with a NUL byte instead of newline. |
Related
headPrint the first lines (or bytes) of files; 10 lines by default.lessPage through a file or command output one screen at a time, with search and scrolling in both directions.catConcatenate files and print them to standard output; also the quickest way to see a short file.tacPrint files with their lines in reverse order, last line first.timeoutRun a command with a time limit, and kill it if it is still running when the time is up.- Pipes and pipelinesHow cmd1 | cmd2 connects processes, which exit status a pipeline returns, PIPESTATUS, pipefail, SIGPIPE, and why variables set in a piped loop vanish.