The tac command is essentially the cat command, but its purpose is to concatenate files in reverse. Like cat, it has a convenient fallback mode to print to standard output (STDOUT) if no output file is provided, making it one of those commands that are more often used as a lazy pager—like less and more—than the function it is named for.
The cat command is often overused and abused, and tac is often taken as a joke command like ddate or cowsay. It often gets paraded out in April Fool’s day articles detailing stupid terminal tricks. So, it may come as a surprise that tac actually has a legitimate reason to exist.
It’s actually a useful command.
What is the purpose of tac?
The tac man page does a rather poor job of describing its own function:
Write each FILE to standard output, last line first.
Taking that statement as it’s written, tac should print the last line of a file, then print the file starting back at line one:
$ cat metasyntactic.list foobar foo bar baz $ tac metasyntactic.list baz foobar foo bar
That’s not what it does, though. Its info page is much clearer:
copies each FILE (‘-’ means standard input), or standard input if none are given, to standard output, reversing the records (lines by default) in each separately.
$ tac metasyntactic.list baz bar foo foobar
Ignoring the fact that tac gives you everything in reverse, it has a few surprisingly useful and unique options.
Tac and separators
As the info page indicates, the file doesn’t have to be delimited by line, meaning that tac is equally as effective with, for example, a CSV file. You define a file’s separator character with the --separator or -s option, along with the delimiter used in the file.
For a CSV file, the character is probably a comma (,), but you can define any character. If a file doesn’t terminate with the separator character, though, then you get an unexpected result:
$ tac --separator="," metasyntactic.csv bazbar,foo,foobar
There is no separator character between the first two items. The file’s final record (the string following the final separator, in this case, a comma) is not itself followed by a comma, so it’s treated as a non-record by tac. To account for this issue, use the --before or -b option, which places the separator character before each record:
$ tac --separator="," --before metasyntactic.csv baz,bar,foo,foobar
The separator character doesn’t have to be a single character. It can also be a regular expression (regex).
Tac and regular expressions
A full explanation of regex is out of scope for this article, but it’s worth mentioning that extended POSIX is supported by means of an environment variable. Extended regex greatly enhances the readability of a regular expression, and for the sake of simplicity, that’s what this example uses. Assume you have a file containing strings all separated by integers:
$ cat metasyntactic.txt foobar123foo456bar789baz898
You can reliably predict that the strings you care about are separated by integers, but you cannot reliably predict what those integers will be. That’s exactly the problem regex is meant to solve.
To use regex in your tac command, use the --regex or -r option before your --separator definition. Also, unless it’s already set in your environment, you must activate the REG_EXTENDED environment variable. You can set this variable to anything but zero to activate it, and you can do that in all the usual ways:
- Export the variable for the shell session you’re using.
- Set the environment variable in your shell configuration file (such as ~/.bashrc).
- Prepend the environment variable to the tac command (in Bash, Zsh, and similar), as shown in the example below:
$ REG_EXTENDED=1 tac --regex \ --separator='[0-9]+' metasyntactic.txt 89baz898bar765foo432foobar1
The regex option doesn’t handle non-terminated records well, though, even using the --before option. You may have to adjust your source file if that feature is important to you.
When to use tac
These simple yet useful parsing options make tac worth using as an uncomplicated, minimalist parsing command. For those simple jobs that aren’t quite worth writing an AWK or Perl expression for, tac just might be a sensible solution.
The tac command is limited, obviously, because it doesn’t manipulate records in any way aside from reversing them. But sometimes that’s the only list manipulation you need.
For instance, if you’re packaging software for distribution, it’s not unusual to have a list of dependencies that are required for installation. Depending on how you gathered this list, you may have it in the order you established the dependencies were required instead of the order in which they must be installed.
This practice is relatively common because compiler errors hit the high-level dependencies first. That is, if your system is missing libavcodec then GCC stops and alerts you; but since GCC hasn’t gotten a chance to probe your system for libvorbis and libvpx, for example, it can’t tell you that those dependencies are also missing (and, often, required to exist on your system before compiling libavcodec).
So, your list of dependency grows in top-down form as you discover what libraries your system needs to build the libraries that the libraries need (and so on). At the end of such a process, tac is the quick and easy way to reverse that list.
Another common annoyance is log files. Entries are generally appended to a log file, so admins use tail to see the latest errors. That works well, but there are times you want to see a "chunk" of entries without knowing how far back you need to go. The tac command piped to less or more puts the latest entries at the top of your screen.
Finally, many configuration files have no clear termination marker for a given section. You can look up awk and sed commands to devise a way to determine when a block in a config file ends, or you can use tac to reverse the order such that once your parser has found the first relevant entry in that block, it also knows when to stop reading, because what used to be the header is now a footer.
There are plenty of other great uses for tac, and probably a bunch of reasons that tac is too rudimentary to be a solution. Your system likely has it installed, however, so remember this command the next time you find that edge case in your workflow that really really needs to be attacked in reverse.