Also known as reverse cat, tac a simple command-line utility that lets you reverse lines in output using the | builtin pipe operator and tac. That is, if you have a command, left-hand side (lhs), and want to reverse the contents of its output, all you would do is type lhs | tac. That’s it!
Admittedly, there is more to tac than meets the eye. Don’t worry. We’ll cover it all, in good time.
Advice on tac
To use or not to use, that is the question. You won’t you don’t want to tac when you don’t have to. However, if you want to spread tac as your bread and butter command that is up to you. In the meantime, here is my advice on tac taking both sides to remain as neutral.
When to use tac
There are times when to use tac that help you get more out the command line with less code and time spent researching lhs command options.
When you are not sure about the options of lhs
Many commands like sort come with an option to reverse the output of a command. However, if you are not sure whether or not a command on the left-hand side has a -r option to reverse output, using tac is a sure way to reverse the output lines.
When performance doesn’t matter
Although insignificant, most commands used in conjunction with a built-in option to reverse output perform better than piping the output to tac. So if a little performance lag isn’t an issue, piping into tac to replay output in reverse is okay.
When not to use tac
There are times when you may not use tac because you know better. Here are a few to note.
When you know the command on the lhs has an option to reverse output lines
Like I said, “Most commands come with an option to reverse output.” If you know that a specific lhs command has an option you may not use tac. After all, -r is shorter than – | tac.
When performance matters
Like I said, “Using the lhs reverse option may perform better than tac.” If you are looking to squeeze out a few seconds in a bash script or are dealing with larger files that require time to read, you may not use tac.
Tac help
Running the help command for tac or man tac shows the usage along with options that may be used. Here is what to expect.
Commands
Output
Tac version
What version am I?
You are the latest version of yourself. However, in the case of what version your tac is, there is an option for that.
Commands
Output
Notes
If you are using tac on FreeBSD or macOS, the long option for version may not be available. In that case, try -v or man tac. If you’ve tried it let me know. I am curious. Thanks
Tac options
Beside help and version, tac doesn’t have many options. For what it has, you are sure to find out that tac is not just any old reverse cat.
Tac before option
The -b option allows you to change how the separator is attached in output. By default, the newline separator is attached after each line.
I know it’s confusing. Let’s break it down by example.
First, let’s see what our output looks before using tac -b.
Commands
Output
Now let’s see what our output turns into after using tac without -b.
Commands
Output
Now let’s see what the output turns into using tac -b.
Commands
Output
Tac separator option
The separator option -s ‘literal string’ allows you to specify the character or sequence of characters used by tac to tell lines apart. By default, the newline character (‘0a’ in hex) is used.
How to use the tac separator option is not obvious at first. However, once you know it’s there, it is hard not to try to use it.
Consider the following example, operating on lines represented in hex.
Commands
Output
Notes
(1) It might seem trivial as using the seq 20 | tac command, however, in that case, we didn’t spend time working on the output stream in hex. Using this pattern is useful when the separate is not something trivial as the new line character such as the zeroth byte.
Now less try using tac on something a little less raw and more meta like simple HTML.
Consider the following file.
File
Commands
file | tac -s "<br>"
Output
We managed to convert the HTML page
B
C
into
B
A
using tac.
Suppose that you need to do something a little more complicated like treat any tag as a tac separator. In that case, you are not going to get away with just using the separator option alone. That is where the regex option comes in. Combined with the separator option it enables you to do more with the tac command than reverse a line in a file. Here’s how.
Tac regex option
The regex option -r -s ‘regex’ allows you to specify that the separator string is to be treated as a regular expression.
How to use the tac regex option is as simple as adding the -r option before or after the separator.
Consider the previous example using the regex option in conjunction with the separator option. Let’s have tac treat any markup tag as a separator.
File
<a href="#simple-functions" aria-label="simple functions permalink" class="anchor">
</a>simple functions</h3>
<p>Functions are simple in bash. At least this one is. It puts a string on the screen. </p>
<p>Commands</p> <div class="gatsby-highlight" data-language="bash">
<pre class="language-bash"><code class="language-bash">simple-function
<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span> <span class="token keyword">echo</span>
as simple as it gets <span class="token punctuation">}</span> simple-function</code>
</pre></div>
Source: https://temptemp3.github.io/bash-functions
Commands
{
echo ‘<h3 id="simple-functions">
<a href="#simple-functions" aria-label="simple functions permalink" class="anchor"></a>
simple functions</h3> <p>Functions are simple in bash. At least this one is. It puts a
string on the screen. </p> <p>Commands</p>
<div class="gatsby-highlight" data-language="bash">
<pre class="language-bash"><code class="language-bash">simple-function
<span class="token punctuation">
(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">echo</span> as simple as it gets
<span class="token punctuation">}</span>
simple-function</code></pre></div> ‘
}
file | tac -r -s "]*."
Output
We managed to convert the HTML page reversing the file using HTML tags. If you look into the details, you will notice that it isn’t perfect yet.
Tac command in pure bash
Here is a primitive version of tac implement in pure bash that reverses the lines piped in by an lhs command. Future versions are left as an exercise.
## tac
## version 0.0.1 – initial
##################################################
tac() {
local -a arr
local -i i
mapfile arr –
i="${#arr[@]}"
while [ ${i} -ge 0 ]
do
echo ${arr[i]}
let i-=1
done
}
##################################################
tac
##################################################
Source: https://github.com/temptemp3/ba.sh/blob/master/tac.sh
Tac command using other commands
Here are some primitive versions of tac implement using other commands that reverse the lines piped in by an lhs command. Future versions are left as an exercise.
Before we get started, close your eyes and think, “What could be used to implement a primitive version of tac?”
Many commands come to mind but I will focus on those that we have room for.
gawk
Similar to the Tac command in pure bash example, to implement tac we would first store the read lines to be replayed in reverse after all the lines are read. Here is how it would look using gawk.
gawk '{ line[++line[0]]=$(0) } END { for(i=0;i<line[0];i++) print line[line[0]-i] }' -
Now try using on the lhs command seq 10.
seq 10 | gawk ' { line[++line[0]]=$(0) } END { for(i=0;i<line[0];i++) print line[line[0]-i] } '
As you would expect the output is
Exercises
1. The function lhs() { seq 10 ; } lists the integers 1 through 10. Write out an rhs command such that lhs | rhs equals 10864213579 only using tac (Hint: see Tac before option example)
2. Reverse the output of rhs() { find -mindepth 1 -maxdepth 1 -print0 ; } using tac (Hint: see Tac separator option example)
3. Extend tac.sh (in Tac command in pure bash) to behave exactly like tac. You will need to add options and make sure to test their behavior.
4. Implement a primitive version of tac in pure bash as a recursive function.
TL;DR
Again, I enjoyed writing the Bash tac command. After reading I hope that you can agree that there is more to tac than you thought. Also, after trying to do things the hard way near the bottom, I hope that you know how useful the tac command could be. If anyone manages to complete any of the exercises or need help on their homework let me know. Thanks,