Read Files Last Line First with tac

GNU tac prints a file from its last line to its first, which sounds trivial until you actually need it in the middle of a pipeline. This guide covers reversing a plain file, reading a pipe, handling records separated by something other than a newline, and saving the result without wrecking the original. These examples use GNU tac from coreutils 9.4, installed here as package version 9.4-3ubuntu6.3.

Allow about ten minutes. You need a shell and a readable text file. The normal examples only read input and write to standard output, so they need no elevated privileges. The command does not edit its input file unless you redirect its output over that same file, which is a destructive mistake worth avoiding on its own.

1. Reverse the lines of a file

Run tac with the file name. With no options, the newline is the separator and each line is written in reverse order:

$ printf 'first\nsecond\nthird\n' > /tmp/lines.txt
$ tac /tmp/lines.txt
third
second
first

The output goes to standard output. The input file itself is untouched:

$ cat /tmp/lines.txt
first
second
third

Checkpoint: If the original file still prints in its original order, that confirms tac did not modify it.

2. Reverse a pipeline or standard input

When no file is named, tac reads standard input. A file name of - does the same job, which is handy when a file argument is chosen by a script:

$ printf 'one\ntwo\nthree\n' | tac
three
two
one

$ printf 'one\ntwo\n' | tac -
two
one

This gives you a simple last-record-first pipeline; journalctl output piped through tac is a common quick visual check. Do not treat it as a complete log analysis method: multi-line entries can contain newlines, and reversing text does not understand timestamps or event boundaries.

3. Use a custom separator

Use -s or --separator when records are separated by something other than a newline. The separator stays attached to a record, and by default it is attached after the record; -b or --before attaches it before instead.

$ printf 'alpha::beta::gamma\n' > /tmp/records.txt
$ tac -s '::' /tmp/records.txt
gamma
beta::alpha::
$ tac -b -s '::' /tmp/records.txt
::gamma
::betaalpha

The newline at the end of the sample belongs to the first record, so it appears after gamma in the reversed output. That is not a bug: it follows directly from treating ::, rather than newline, as the boundary. For clean interchange data, choose a separator that cannot occur inside the records, or parse the format with a tool that understands its own escaping rules.

Tip: Inspect unusual output with a byte-oriented view such as od -An -tc. It shows exactly whether a newline or separator is present, without relying on how your terminal wraps the text.

4. Treat the separator as a regular expression

Add -r or --regex when the separator is a pattern rather than a literal string. This helps when any of several single characters can separate records:

$ printf 'red,green;blue\n' > /tmp/colours.txt
$ tac -r -s '[,;]' /tmp/colours.txt
blue
green
red,

Here the comma and semicolon are both boundaries, but they are still separators, not discarded data. Test the expression against a small copy first, especially if it can match an empty string or several characters at once: a broad pattern produces surprising record boundaries, and tac never validates that the result matches your application's format.

5. Preserve the original when saving reversed output

Shell redirection truncates its destination before tac even starts. Never use this form when the destination is also the input:

$ tac /tmp/lines.txt > /tmp/lines.txt

Depending on timing and buffering, that can leave the file empty or incomplete. Write to a new path, verify it, then replace the original only if the replacement is intentional:

$ tac /tmp/lines.txt > /tmp/lines.txt.reversed
$ test -s /tmp/lines.txt.reversed && tac /tmp/lines.txt.reversed
third
second
first
$ mv /tmp/lines.txt.reversed /tmp/lines.txt

That final mv changes state and removes the old name, so keep a backup first if the original matters:

$ cp --preserve=all /tmp/lines.txt /tmp/lines.txt.backup

Recovery: If the replacement turns out wrong, restore the backup with mv /tmp/lines.txt.backup /tmp/lines.txt. Do not remove the backup until the new file has been checked.

6. Check failures and version details

A successful exit status only means tac completed its read and write operations; it does not prove a separator expressed your file format correctly. Check both the status and the actual result when scripting:

if tac --separator='::' input.txt > output.txt; then
    printf '%s\n' 'reverse operation completed'
else
    status=$?
    printf 'tac failed with status %s\n' "$status" >&2
    exit "$status"
fi

Use tac --help and tac --version to inspect the installed build; on this machine that is GNU coreutils 9.4. The documented options are -b, -r, -s, --help and --version. There is no option that edits a file in place, and no need for sudo merely to reverse readable data.

Done means