Is there any clean, clear-cut POSIX equivalent to tac?
Readability as well as succinctness should both be considered.
The cleanest POSIX equivalent would be
tail -r
as
-r Reverse. Copies lines from the specified starting point in
the file in reverse order. The default for r is to print the
entire file in reverse order.
has been accepted for the next POSIX issue (and hopefully, it will be soon supported on all platforms).
If tail -r is not available the "classic" text processing tools can be successfully used - as you and others have shown - to reverse the lines in a file.
Readability and conciseness aside, even old ed can do it:
ed -s infile <<\IN
g/^/m0
,p
q
IN
or, if it's the output from a pipeline that you want to reverse - read it into the text buffer first:
ed -s <<\IN
r ! your | pipeline | goes | here
g/^/m0
,p
q
IN
You can do this with a sed one-liner as follows, though it is certainly not readable for the "uninitiated":
sed -n '1h;1!{x;H;};${g;p;}' file.txt
Explanation:
-n suppresses sed's default action of printing each line.
1h causes the first line to be stored in the hold space.
1!{...} applies this block of commands to all lines except for the first one.
x swaps the hold space and the pattern space. (The pattern space is where each line is stored while it is being processed.) Then H appends to the hold space a newline followed by the (new) contents of the pattern space.
The final block ${...} is only applied when the last line is reached. g gets the contents of the hold space and puts it in the pattern space, and p prints the pattern space.
Of course this depends on being able to hold the entire file in memory at once; it may fail on extremely large files.
sed '1!x;H;1h;$!d;g' file
tac(){sed '1!x;H;1h;$!d;g'}
Here's an awk script that stores the whole file in memory:
awk '{line[NR]=$0} END {for (i=NR; i>=1; i--) print line[i]}' file
Phrased as a shell function:
tac () { awk '{line[NR]=$0} END {for (i=NR; i>=1; i--) print line[i]}' "$@"; }
Here's a clean clear-cut POSIX solution for on-disk files:
#!/bin/sh
function tac () {
lines=$(wc -l < "$1")
while [ $lines -gt 0 ]
do
head -n $lines "$1" | tail -n 1
lines=$((lines-1))
done
}
The main down-side is that it reads the file once for every line in the file. POSIX doesn't specify an upper limit for -n number, so large files might overrun an implementation's choice. POSIX does limit arithmetic expansion to signed long integer, though (circa 2,147,483,647).
A similar construct could be made from tail -n -$lines input | head -n 1.
Given the requirement to read the file so many times, these are likely less performant than tac.
wc includes the filename in the output, so [ $lines -gt 0 ] will break, won't it? (I thought the filename in the output was standard.)
wc < file instead of wc file
POSIX vi has you covered, as does ed or ex.
I prefer vi myself if I am going to be looking at the output anyway.
Command Mode, of course:
:g/^/m0
You can always give :g a range if you don't want to reverse the whole file, as long as you pay attention to where and how you are moving things:
:1,10g/^/m0
You can script it with ed or ex using something like this:
$ ed -s infile <<IN
g/^/m0
,p
w
IN
vs
$ ex -s infile <<EOS
g/^/move0
wq
EOS
I do it all the time.
The "-r" switch to tail is not POSIX-compliant at the time of this post:
tail [-f] [-c number|-n number] [file]
tac.exversion to it first, and show its use in a pipeline.)exto accomplish the same.) If you don't want to post here, I will do so myself, borrowing from your answer elsewhere. :)