>
Open Source

head, tail, and cat cover more Linux file work than you think

I have watched sysadmins open a 200 MB log in nano, scroll for ten minutes, then give up and grep for the line they wanted. The same job takes two seconds with tail. Most people learn three or four flags for these commands and stop there, which leaves a lot of useful behavior on the table.

Every Linux system generates files constantly: config files, log files, CSV exports, deployment outputs, cron job records. The three commands in this guide ship with every distribution and have been around since the early Unix days. They look simple, and they are simple at the start. Each one also has flags that solve specific problems the first time you hit them.

head: more than the first 10 lines

head prints the first 10 lines of a file by default. The basic syntax is head [options] [file(s)]. The flag most people know is -n, which controls how many lines to show. Both head -n 5 /var/log/syslog and head -5 /var/log/syslog work.

Two flags are worth more than they look. -c N limits output by bytes instead of lines, useful for inspecting the start of a binary file or grabbing a fixed-size header from a log: head -c 50 /var/log/syslog. -n -N is the lesser-known gem: it prints everything except the last N lines. That is the cleanest way to strip footer rows from a CSV before piping into another tool: head -n -5 access.log.

Pass multiple files and head adds a header before each one so you can tell which output belongs to which file:

head -5 /etc/passwd /etc/group

The ==> markers separate the two outputs cleanly.

Flags worth memorizing first

A short set covers most needs without reading the man page:

  • -n N controls how many lines to show (-5 and -n 5 are equivalent)
  • -c N limits output by bytes, useful for inspecting file headers or grabbing a fixed-size chunk
  • -n -N prints everything except the last N lines, the cleanest way to strip CSV footers
  • -q silences the file headers when passing multiple files, useful in scripts where the markers add noise

tail: the workhorse for live logs

tail prints the last 10 lines by default. The syntax is the same as head: tail [options] [file(s)]. The -f flag is the one most sysadmins learn first because it is how you watch a log while a service runs. tail -f /var/log/nginx/error.log keeps the file open and prints new lines as they arrive. Press Ctrl+C to stop. Combining -f with -n 50 shows the last 50 lines first, then continues following: tail -n 50 -f /var/log/nginx/access.log.

The flag that saves real time on production servers is --follow=name (-F is the shorthand). When log rotation replaces a file with a new one (the standard pattern with logrotate), tail -f keeps following the original file descriptor, which now points at the rotated-out file. The new file gets new writes, and you are watching the wrong place. --follow=name makes tail follow the file by name and reopen when the inode (the underlying file identifier on the filesystem) changes. This is the difference between watching the active log and watching a dead one after a 3 AM rotation.

For systemd systems where many services write to the journal instead of flat files, journalctl -u nginx -f does what tail -f does for unit logs. The -n 50 and -f flags combine the same way: journalctl -u sshd -n 50 -f.

A few more flags are worth knowing. tail -n +2 /etc/passwd skips the first line and starts with line 2, useful when the first line is a CSV header. tail -c 100 /var/log/syslog shows the last 100 bytes, which is the format you want when capturing a tail snippet to paste into a chat.

cat: the verb that just concatenates

cat reads files and writes them to standard output. Most use of it is dumping a file to the terminal: cat /etc/os-release. That is fine, and it is the least interesting thing cat does.

Concatenating files into a new one is the actual job the name describes:

cat header.txt body.txt footer.txt > report.txt

You can also concatenate inline with cat reading redirected input, then type into the file and finish with Ctrl+D:

cat > notes.txt
This is line one
This is line two
Ctrl+D

A practical pattern is using cat as the source for a pipeline when a single file needs filtering: cat /var/log/syslog | grep error. Most shell users would just write grep error /var/log/syslog and skip cat entirely, which is the right call. The Useless Use of Cat award exists for a reason, but for files where you genuinely want to see the source before piping, cat keeps the command readable.

The cleanest non-obvious use of cat is showing hidden characters that would otherwise trip a shell script. Use cat -A to display tabs as ^I, line endings as $, and non-printing bytes in a visible form. When a config file looks fine in the editor but the script keeps failing on whitespace, cat -A is the fastest diagnostic.

Three recipes I actually use

A few combinations cover most file-inspection work. To follow a log starting from the last 100 lines and keep watching through rotations:

tail -F -n 100 /var/log/nginx/access.log

To inspect the first 20 and last 20 lines of a file in one pass:

{ head -n 20 /var/log/syslog; echo '...'; tail -n 20 /var/log/syslog; }

To strip trailing whitespace from a config file in place, after a backup:

cp file.conf file.conf.bak && cat file.conf | sed 's/[[:space:]]*$//' > file.conf.tmp && mv file.conf.tmp file.conf

None of these are clever. They are the small set of moves that get reused across server administration, log triage, and config-file edits.

Common patterns across the three commands

A few moves combine these tools in ways that pay off across day-to-day shell work. To compare the first 20 lines of two log files side by side, diff <(head -20 a.log) <(head -20 b.log) uses process substitution (a shell feature that lets you treat the output of a command like a file) to feed each file into diff without writing intermediate files. To grab the line that just appeared in a live log and save it to a separate file, tail -n 1 -f /var/log/nginx/access.log >> watch.log runs in the background and appends new entries.

When a config file looks fine in the editor but the parser keeps failing on it, the issue is usually invisible whitespace. cat -A file.conf displays tabs as ^I and line endings as $. A line that ends with trailing spaces shows the extra characters. That is almost always the cause when YAML, INI, or systemd unit files refuse to parse cleanly.

For splitting a large file into chunks without opening an editor, a loop over head and tail works:

split -l 1000 bigfile.txt chunk_

That is faster and more reliable than head -1000 bigfile.txt > part1 && tail -n +1001 bigfile.txt > part2 because it does the split in one pass without re-reading the file.

Trade-offs

The cost of these commands is that they scale linearly with file size. Reading a 5 GB log file with cat is wasteful even if you are piping it to grep, because cat reads the whole file before grep starts filtering. The fix is to feed grep the file directly (grep pattern /var/log/big.log) so the filter can short-circuit. For really large files, less (with /pattern to search) or awk/rg are the right tools.

For log rotation specifically, tail -F is the right flag but only works when the rotation tool preserves the file name. Some applications use copytruncate (copy the file, then truncate the original in place, so the inode stays the same). In that case --follow=name and --follow=descriptor behave the same, and either works. Other rotation tools rename the file and create a new one with the original name, which is where --follow=name matters.

Live monitoring with tail -f competes for I/O when the log is being written at high volume. On a busy web server, tail -f adds noticeable load to the disk subsystem. For real-time traffic analysis, a structured log shipper (Vector, Filebeat, Promtail) is a better fit, and you query the indexed logs downstream.

In practice, learning the -F, -n +N, -c, and cat -A flags takes about fifteen minutes and saves hours over the next year. One thing that is harder than expected is teaching yourself to use head -n -N instead of trying to count tail lines and reverse the file. One thing that is easier than expected is chaining head and tail together with process substitution (diff <(head file1) <(head file2)) for side-by-side comparisons.

If you spend any time at a Linux shell, these three commands are the cheapest productivity upgrade available. If you find yourself reaching for nano or vim to inspect log files, switching to tail -f first will change how you triage incidents.

Bottom line

head, tail, and cat cover 90% of “I just want to look at this file” work without ever opening an editor. Memorize -n, -F (the rotation-safe tail flag), -n +N, -c, head -n -N (skip last N), and cat -A for hidden-character diagnostics. Reach for less or grep when the file is too large to read, and reach for sed or awk when you need to transform, not just view. The three commands are not a substitute for those tools; they are the foundation you build on.

Leave a comment