Intro To 'tac' Command In Linux
2023-08-02 - By Robert Elder
I use the 'tac' command to print files in reverse order:
tac numbers.txt
The 'cat' Command Versus 'tac' Command
Here, I have a file called 'numbers.txt':
one
two
three
four
five
six
seven
eight
By default, the 'tac' command works just like the 'cat' command:
cat numbers.txt
one
two
three
four
five
six
seven
eight
except the 'tac' command prints the lines in a file in reverse order:
tac numbers.txt
eight
seven
six
five
four
three
two
one
Note that the word 'tac' is the word 'cat' spelled backwards.
An Example Use Case Of The 'tac' Command
Here, I have a file called 'text-editors.txt' that contains the following text:
emacs
is better than
vim
This statement is clearly incorrect. I can use the 'tac' command to correct the statement in the file like this:
tac text-editors.txt
vim
is better than
emacs
Now it correctly says 'vim is better than emacs' (which is a universally accepted fact).
Using A Custom Separator
I can also use the '-s' flag to specify a custom separator, like a dash character, and the order of the items between the separators will be reversed:
echo -n "one-two-three-" | tac -s '-'
three-two-one-
Reversing Bytes In A File Doesn't Work
The documentation of the 'tac' command states that you can use this regular expression based separator to reverse the order of all bytes in a file:
info tac
...
Example:
# Reverse a file character by character.
tac -r -s 'x\|[^x]'
...
On my machine, this example does correctly reverse the order of these bytes:
echo -en "\x00\x01" | tac -r -s 'x\|[^x]' | xxd
00000000: 0100 ..
but it does not reverse the order of these bytes:
echo -en "\x80\x81" | tac -r -s 'x\|[^x]' | xxd
00000000: 8081 ..
Further Investigation Into Byte Reversing
When I was writing this article, I expected the above 'byte reversing' example to 'just work' for all bytes, but as you can see it did not. During my tests, I wrote the following script when testing the 'tac' command which I will include here:
for i in {1..100}
do
head -c 2 /dev/urandom > $i.test;
cat $i.test |
tac -r -s 'x\|[^x]' |
tac -r -s 'x\|[^x]' > $i.test.rev;
md5sum $i.test >> before;
md5sum $i.test.rev >> after;
done
The above script will create 100 files with two random bytes in them. For each file, it will use the 'tac' command to reverse the file twice. The md5 hash of both the before and after files is calculated for every file. Reversing a file twice should (in theory) return its contents to the original value, so the 'before' and 'after' files should have all the same hash values. Using this 'vim' command, I compare the contents of these files:
vim -d before after
And here is the result:
Clearly, the 'tac' command is not an appropriate tool if you want to reverse all of the bytes in a file.
Why Doesn't 'tac' Correctly Reverse Bytes?
Here were the inputs for the test cases that failed:
00000000: 6dcc m.
00000000: 028d ..
00000000: 7da2 }.
00000000: 78f2 x.
00000000: 3ed9 >.
00000000: 18b5 ..
00000000: 7089 p.
00000000: 50a2 P.
00000000: 22c0 ".
00000000: 1098 ..
00000000: 6bbf k.
00000000: 5996 Y.
00000000: 5dce ].
00000000: 62a4 b.
00000000: 7983 y.
00000000: 34df 4.
00000000: 5cd0 \.
00000000: 4cfb L.
00000000: 0a8b ..
00000000: 16db ..
00000000: 70ae p.
00000000: 61bf a.
00000000: 5282 R.
00000000: 41cc A.
And here were the inputs for the test cases that passed:
00000000: fccb ..
00000000: da2d .-
00000000: 5b12 [.
00000000: 435f C_
00000000: 3667 6g
00000000: a7eb ..
00000000: f17b .{
00000000: c4e2 ..
00000000: 3d2b =+
00000000: c4d0 ..
00000000: 2d69 -i
00000000: 5e1c ^.
00000000: ca24 .$
00000000: e6ff ..
00000000: 8944 .D
00000000: 371e 7.
00000000: e6f6 ..
00000000: 96da ..
00000000: 2f47 /G
00000000: b237 .7
00000000: b00a ..
00000000: a2fd ..
00000000: ee89 ..
00000000: d856 .V
00000000: 3327 3'
00000000: b52f ./
00000000: 520d R.
00000000: a4e2 ..
00000000: cedd ..
00000000: b0fa ..
00000000: f6c1 ..
00000000: f0ca ..
00000000: 8921 .!
00000000: 8eea ..
00000000: ab77 .w
00000000: ff99 ..
00000000: 3d0b =.
00000000: a4d4 ..
00000000: bd92 ..
00000000: 8d37 .7
00000000: 284e (N
00000000: b9b2 ..
00000000: 9c28 .(
00000000: e463 .c
00000000: 1e03 ..
00000000: 889b ..
00000000: 6c6a lj
00000000: 97bb ..
00000000: 7a1f z.
00000000: e5d0 ..
00000000: 8681 ..
00000000: bf82 ..
00000000: 1801 ..
00000000: 040c ..
00000000: 4b09 K.
00000000: 8c2d .-
00000000: eb9c ..
00000000: 8e1c ..
00000000: f7f9 ..
00000000: 4b57 KW
00000000: ba7d .}
00000000: 1053 .S
00000000: 9772 .r
00000000: eb48 .H
00000000: f978 .x
00000000: c1b3 ..
00000000: e038 .8
00000000: d01c ..
00000000: 9cb7 ..
00000000: d9de ..
00000000: 193a .:
00000000: 3015 0.
00000000: 6340 c@
00000000: 978a ..
00000000: 84a2 ..
00000000: 9a0e ..
The common theme above appears to be that the failing cases had an ASCII character in the first byte and a non-ASCII character in the second byte. Perhaps this is a bug? Or, perhaps this is expected behaviour (or potentially undefined behaviour) in the flavour of regular expression that the 'tac' command uses? I am not sure, and I've decided that I don't really care.
And that's why the 'tac' command is my favourite Linux command.
Intro To 'stty' Command In Linux
Published 2023-10-04 |
$1.00 CAD |
Intro To 'nproc' Command In Linux
Published 2023-07-15 |
Intro To 'comm' Command In Linux
Published 2023-09-06 |
How To Force The 'true' Command To Return 'false'
Published 2023-07-09 |
A Surprisingly Common Mistake Involving Wildcards & The Find Command
Published 2020-01-21 |
A Guide to Recording 660FPS Video On A $6 Raspberry Pi Camera
Published 2019-08-01 |
Intro To 'chroot' Command In Linux
Published 2023-06-23 |
Join My Mailing List Privacy Policy |
Why Bother Subscribing?
|