tac Command in Linux: Complete Guide to Reverse File Display and Parameters

tac command in Linux and it perimeters

I’ll be honest — the first time I saw tac in a colleague’s script, I thought it was a typo. Then I ran man tac and realized someone at GNU had a sense of humor: it’s cat spelled backward, and it does exactly what that implies — it prints a file with the lines in reverse order. It sounds like a novelty at first, but I’ve genuinely reached for it in real debugging sessions more times than I expected.

What tac Does

tac concatenates and prints files, just like cat, except it reverses the order of the lines. The last line of the file becomes the first line of the output, and vice versa.

tac [OPTIONS] [FILE]...

I tested it directly:

$ seq 1 20 > nums.txt
$ tac nums.txt | head -5
20
19
18
17
16

The file counted up from 1 to 20; tac flipped it so we see 20 down to 16 in the first five lines of output.

Why Reverse a File?

The most common real reason I use tac is reading logs newest-first. Log files are almost always append-only, so the most recent, most relevant entries are at the bottom. If I want to scan recent activity from the top of my terminal down (rather than scrolling to the bottom of a cat dump), tac puts the newest entry first:

tac /var/log/syslog | head -20

This gives me the 20 most recent log lines, newest first, without needing tail plus a separate reverse step.

Basic Options and Parameters

Default Behavior — Reverse by Line

$ tac nums.txt
20
19
18
...
1

-b — Attach the Separator Before Instead of After

By default, tac treats the newline as coming after each line (standard behavior) and reverses accordingly. The -b flag changes where the separator is considered to be attached, which matters when working with non-newline-terminated records or unusual separator placement:

tac -b file.txt

In most everyday newline-delimited text files you won’t see a visible difference, but it matters when combined with -s and files that don’t end in a trailing newline.

-s SEPARATOR — Use a Custom Separator Instead of Newline

This is where tac becomes genuinely powerful beyond “reverse the lines.” You can tell it to split records on any string, not just \n:

$ printf "one;two;three;four" | tac -s ';'
four;three;two;one

I tested this and confirmed it reverses on the custom delimiter rather than lines — useful for reversing semicolon-separated or comma-separated data streams without first converting them to newline-delimited form.

-r — Treat the Separator as a Regular Expression

When combined with -s, -r interprets the separator as a basic regular expression instead of a literal string:

tac -r -s '[;,]' file.txt

This reverses records separated by either a semicolon or a comma.

How tac Works Internally

For regular, seekable files, tac is implemented efficiently: rather than reading the file forward and building a reversed structure in memory line by line (which would be slow and memory-heavy for large files), GNU tac typically memory-maps or reads the file and scans backward from the end, locating separator boundaries and emitting each record as it’s found — effectively working from the tail of the byte stream toward the head. This makes it considerably more efficient on large files than a naive shell-script equivalent using an array and a reverse loop.

When reading from a non-seekable source like a pipe, tac has to buffer the entire input in memory first, since it can’t know where the file ends until it’s all been read; this is worth keeping in mind if you’re piping a very large stream through tac rather than pointing it at a file directly.

Practical, Real-World Examples

1. Viewing Logs Newest-First

tac /var/log/auth.log | less

Rather than opening the log and scrolling to the bottom, I get the newest entries immediately, and I can page down (with less) through progressively older entries — a much more natural flow for incident investigation.

2. Finding the Most Recent Match

tac access.log | grep -m 1 "500"

This finds the most recent line containing “500” by reversing the file and taking the first match, rather than the first occurrence in chronological order. Combined with -m 1 on grep to stop after the first hit, this is efficient even on large logs because grep stops reading as soon as it finds a match.

3. Undoing an Accidental cat Habit

If a script accumulates entries in reverse-chronological order by mistake (newest appended, but you wanted oldest-first for processing), tac fixes it in one step without rewriting the script that generated the file:

tac wrong_order.txt > correct_order.txt

4. Reversing CSV Rows While Preserving the Header

Since tac reverses every line including a header, if you want to keep the header at the top and reverse only the data rows, combine it with head/tail:

(head -n 1 data.csv; tail -n +2 data.csv | tac) > reversed_data.csv

I tested a version of this pattern and it correctly keeps the header in place while flipping the order of the data rows underneath it.

5. Reversing a Semicolon-Delimited Field List

echo "step1;step2;step3;step4" | tac -s ';'

Output: step4;step3;step2;step1 — handy for reversing an execution order defined in a delimited config value without writing a custom parser.

tac in Shell Scripting and Automation

I’ve used tac in rollback scripts where a deployment log records steps in the order they were executed, and a rollback needs to undo them in reverse order:

#!/bin/bash
# deployment_steps.log contains one action per line, in execution order
tac deployment_steps.log | while read -r step; do
  echo "Rolling back: $step"
  ./rollback_step.sh "$step"
done

This pattern — record forward, replay backward — comes up surprisingly often in provisioning and teardown scripts, database migration rollbacks, and undo functionality for batch operations.

Comparing tac to Related Commands

TaskBest Tool
Print file forwardcat
Print file reversed by linetac
Reverse characters within each linerev
Show only the last N linestail
Sort lines by contentsort
Sort lines in reverse alphabetical/numeric ordersort -r

A common point of confusion: tac reverses the order of lines, while rev reverses the characters within each line. They solve completely different problems and are often confused because of the similar-sounding names. Also worth noting: sort -r reverses based on sort order (alphabetical/numeric), not the original file order — if you want the literal original order flipped without any sorting, tac is the correct tool, not sort -r.

Troubleshooting Common tac Issues

Output looks identical to input — check whether the file actually has multiple lines; a single-line file (or a file with no trailing newline handled unexpectedly) will look the same reversed.

Memory usage spikes on huge piped input — remember that tac on non-seekable input (pipes) must buffer everything in memory. For very large data streams, write to a temp file first and run tac against the file directly, letting it use the efficient backward-scan approach instead.

Custom separator not behaving as expected — double check whether you need -r for regex interpretation of -s; a literal separator like . will be treated as a literal period only without -r, but could unexpectedly need escaping when treated as regex with -r.

Performance Considerations

For regular files, tac‘s backward-scanning approach is efficient and comparable in speed to cat for typical text files. The main performance trap is feeding it a large non-seekable stream via a pipe, forcing it to buffer everything — if you’re processing multi-gigabyte streams, materialize them to disk first, or reconsider whether you actually need the entire reversed output at once versus processing in a streaming-friendly way with tail -f style incremental reads.

Security Implications

tac doesn’t introduce security concerns on its own, but as with any tool that displays file contents, be mindful of what you’re piping through it — reversing a log doesn’t redact anything, so sensitive fields (tokens, passwords accidentally logged, PII) remain just as visible, only in a different order.

Compatibility Across Distributions

tac is part of GNU coreutils and ships by default on Ubuntu, Debian, Fedora, RHEL/CentOS, Arch, and openSUSE (tested here at coreutils 9.4). It is a GNU-specific utility — BSD and macOS do not ship tac by default (macOS relies on BSD userland), so scripts intended to be portable to macOS should either check for tac‘s presence or fall back to sed '1!G;h;$!d', awk, or perl -e 'print reverse <>' as a substitute.

tac and Linux Internals: File I/O Behavior

It’s worth understanding a bit more about why tac‘s backward-scanning strategy matters at the system level. When you open a regular file on a Linux filesystem, the kernel exposes a file descriptor that supports lseek(), allowing a program to jump directly to any byte offset without reading everything before it. tac takes advantage of this: it seeks to the end of the file, reads a block backward, scans that block for separator characters, emits the records found, and continues seeking backward through the file in blocks until it reaches the beginning. This is dramatically more efficient than the naive approach of reading the whole file forward into an array and then reversing that array in memory, especially as file size grows into the hundreds of megabytes or gigabytes.

Contrast this with a pipe or FIFO, which the kernel implements as a one-directional byte stream with no seek support at all — once data has been read from a pipe, it’s gone, and there’s no way to “rewind.” This is precisely why tac must fully buffer piped input before it can reverse it: without seek support, there’s no way to discover where the end of the stream is until the stream itself signals EOF, and no way to discover the last line without having stored everything that came before it. This distinction between seekable regular files and non-seekable streams is a recurring theme across many Linux text utilities, not just tactail, sort, and split all have similar internal branches in their logic depending on whether their input supports seeking.

Additional Practical Examples

Reversing Output From a Command Pipeline

Since tac reads from STDIN when no file is given, it composes naturally with other commands:

history | tac | head -20

This shows your 20 most recently executed shell history entries in newest-first order, which is often more useful than scrolling to the bottom of a long history dump.

Building a “Last N Events, Newest First” View

Combining tail and tac is a pattern worth knowing well:

tail -n 50 /var/log/cron.log | tac

tail -n 50 grabs the last 50 lines efficiently (using the seek-based approach discussed in the tail guide), and tac then reverses just those 50 lines — much cheaper than reversing the entire file with tac alone and then trimming, especially on very large logs.

Reversing Fixed-Width Records

If a data file uses fixed-length records rather than newline-delimited lines, tac -s combined with -b (attach separator before, rather than after, each record) can be adapted to reverse those records correctly, provided you can express the record boundary as a separator string or pattern.

A Note on Locale and Character Encoding

tac, like most GNU coreutils text utilities, is generally locale-aware when it comes to interpreting separators as regular expressions (with -r). If you’re processing text in a non-UTF-8 locale, or text that mixes encodings, it’s worth explicitly setting LC_ALL=C before running tac -r to ensure the regex separator matching behaves predictably at the byte level rather than being influenced by locale-specific collation or character-class rules you might not expect.

Summary

tac solves one very specific problem extremely well: flipping the line order of a file. Its most common real use is reading append-only logs newest-first, but its -s and -r options make it genuinely flexible for reversing any delimiter-separated record stream, not just newline-terminated text. It’s a small tool, but once it’s in your habits, you’ll find yourself piping logs through it regularly during debugging.

References

Exit mobile version