BASH Programming

Bash Tac Command

Tac is one of those commands that you don’t realize until you have already gone and done it, reinvented the wheel, which is not uncommon in the practice of programming whether you are just learning the ropes or are a professional with years of experience to boast, you are awesome. If your hands are clean, then good for you; Either way, you are in for a treat, I am going to unload everything I know about the tac command and more. Read on.

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

tac --help

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

tac --version

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

seq 10

Output

Now let’s see what our output turns into after using tac without -b.

Commands

seq 10 | tac

Output

Now let’s see what the output turns into using tac -b.

Commands

seq 10 | tac -b

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

seq 20 | xxd -ps | tac -s '0a' | xxd -ps -r

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

 A<br>B<br>C<br>

Commands

file () { echo -e " A<br>B<br>C<br>" ; }
file | tac -s "<br>"

Output

We managed to convert the HTML page

A
B
C

into

C
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

<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>

Source: https://temptemp3.github.io/bash-functions

Commands

file ()
{
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.

#!/bin/bash
## 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,

About the author

Nicholas Shellabarger

Nicholas Shellabarger

A developer and advocate of shell scripting and vim. His works include automation tools, static site generators, and web crawlers written in bash. For work he tools with cloud computing, app development, and chatbots. He codes in bash, python, or php, but is open to offers.