Tail I lose

I’ve been using the tail command for about 25 years, so you might think I’d know something about it. But last week, as I was putting together a short shell script (not this one), I opened the man page for tail and learned something new. Two things, actually, which surprised me, as tail doesn’t really do that much.

In the following, assume we have a file called lines.txt with 15 lines of text that look like this:

Line 01
Line 02
Line 03
Line 04
Line 05
Line 06
Line 07
Line 08
Line 09
Line 10
Line 11
Line 12
Line 13
Line 14
Line 15

The first thing I learned had to do with the -n option. I’ve known forever that

tail -n 6 lines.txt

would return the last six lines:

Line 10
Line 11
Line 12
Line 13
Line 14
Line 15

I’ve also known that, for historical reasons, this can be shortened to

tail -6 lines.txt

These are probably the most common ways people use tail and are analogous to the most common ways we use head to get the first bunch of lines. I learned about tail after learning head and sort of assumed that tail was—apart from its -f option for following log files—just as elementary.1

But tail has a trick up its sleeve: if you give the numerical argument to the -n option a leading + sign, the output will start that many lines from the beginning of the file and continue to the end. Like this:

tail -n +6 lines.txt

returns

Line 06
Line 07
Line 08
Line 09
Line 10
Line 11
Line 12
Line 13
Line 14
Line 15

This is really useful. If I wanted to process a file that had 5 lines of header information that I needed to skip over, tail -n +6 would be perfect as the first process in a pipeline. Too bad I didn’t know that a couple of decades ago.

How does tail works if -n is given a negative argument? Just as if the argument were unsigned.

tail -n -6 lines.txt

returns

Line 10
Line 11
Line 12
Line 13
Line 14
Line 15

just like

tail -n 6 lines.txt

This is mathematically perverse, but it fits well in the context of tail. The normal use is to count back from the end of the file, so that’s what an unsigned argument does. Putting a sign in front of the argument is a directional signal, and it makes sense to use + when counting from the beginning of a file and - when counting from the end.

The second thing I learned is the -r option. When used with no other options, it reverses the file.

tail -r lines.txt

returns

Line 15
Line 14
Line 13
Line 12
Line 11
Line 10
Line 09
Line 08
Line 07
Line 06
Line 05
Line 04
Line 03
Line 02
Line 01

which works more or less like the tac command.2 Since tac doesn’t come with macOS, you can use tail -r as a replacement. I wouldn’t normally do this, since I have tac installed on my system (it’s part of the coreutils bundle in Homebrew), but it’s handy to have when writing a shell script—or a Shortcut or Keyboard Maestro macro—for someone who doesn’t feel comfortable installing command line tools.

You can combine -r with -n like this:

tail -r -n 6 lines.txt

returns

Line 15
Line 14
Line 13
Line 12
Line 11
Line 10

As you can see, the reversal takes place on the 6 lines extracted from the end of the file. It doesn’t reverse the lines and then take the last 6 after the reversal.

What about using the + with -r?

tail -r -n +6 lines.txt

returns

Line 15
Line 14
Line 13
Line 12
Line 11

To me, this result makes no sense. For consistency with the previous, non-signed, command, I would expect it to extract from Line 6 through the end and then reverse them.

The manual says

Additionally, this option [-r] changes the meaning of the -b, -c and -n options. When the -r option is specified, these options specify the number of bytes, lines or 512-byte blocks to display, instead of the bytes, lines or blocks from the beginning or end of the input from which to begin the display.

OK. That suggests the sign doesn’t matter, but clearly the sign does matter because we got different output when we included the +. My takeaway is to avoid using -n with -r.

By the way, the GNU version of tail—which, like tac is part of the coreutils bundle—bypasses this mess by simply having no -r option. I’m going to use this as my excuse for not knowing about -r. My formative Unix years were with Linux, which uses GNU utilities like the -rless tail. Because OS X/macOS is a descendant of BSD, its utilities are just a little different.


  1. I’m ignoring here the ways that both head and tail have options for counting by bytes or 512-byte blocks instead of lines. I’ve always known about those options but can’t think of any time I’ve used them. 

  2. tac is cat spelled backwards.