[comp.unix.questions] Reversing a file?

montnaro@sprite.crd.ge.com (Skip Montanaro) (10/03/89)

Does somebody have an elegant shell script for reversing the lines of a
file? I've come up with the following short one:

----------cut----------cut----------cut----------cut----------
#!/bin/sh

read line

if [ $? = 0 ] ; then
    $0
    echo $line
fi
----------cut----------cut----------cut----------cut----------

It has two obvious disadvantages. First, it won't work for very long files,
since it uses Unix processes to simulate a stack of lines.  Second, the
Bourne shell's builtin read command doesn't preserve interword white space,
separating words by the value of the IFS environment variable instead.  It
worked adequately for the task I originally intended, however - reversing
the lines in my appointements file.
--
Skip Montanaro (montanaro@crdgw1.ge.com)

seth@ctr.columbia.edu (Seth Robertson) (10/03/89)

In article <MONTNARO.89Oct2224215@sprite.crd.ge.com> <montanaro@crdgw1.ge.com> (Skip Montanaro) writes:
>Does somebody have an elegant shell script for reversing the lines of a
>file? I've come up with the following short one:

<Recursive code deleted>

Ready?
			   ***************
			   ***************
		           **           **
			   **  tail -r  **
		           **           **
			   ***************
			   ***************

Anything else you want?

-- 
                                        -Seth Robertson
                                         seth@ctr.columbia.edu

ok@cs.mu.oz.au (Richard O'Keefe) (10/03/89)

In article <MONTNARO.89Oct2224215@sprite.crd.ge.com>, montnaro@sprite.crd.ge.com (Skip Montanaro) writes:
>Does somebody have an elegant shell script for reversing the lines of a file?

In BSD systems,
	cat -n File
puts six-digit line numbers and a tab in front of every line.
In System V,
	pr -t -n6 File
will do this.
Now sort the lines in descending order of line number
    |	sort -nr
Now you want to throw away the line numbers.  In System V,
    |	cut -f2-
will do the job.  If you haven't got cut(1),
    |	sed -e 's/^.......//'
will strip off the spaces, digits, and tab.  So

In BSD systems:
	cat -n $* | sort -nr | sed -e 's/^.......//'
In System V:
	pr -t -n6 $* | sort -nr | cut -f2-

lang@PRC.Unisys.COM (Francois-Michel Lang) (10/03/89)

In article <MONTNARO.89Oct2224215@sprite.crd.ge.com> <montanaro@crdgw1.ge.com> (Skip Montanaro) writes:
>Does somebody have an elegant shell script for reversing the lines of a
>file? I've come up with the following short one:

No need to write a script.
tail -r does this already.

----------------------------------------------------------------------------
Francois-Michel Lang
Paoli Research Center, Unisys         lang@prc.unisys.com      (215) 648-7256
Dept of Comp & Info Science, U of PA  lang@linc.cis.upenn.edu  (215) 898-9511

----------------------------------------------------------------------------
Francois-Michel Lang
Paoli Research Center, Unisys         lang@prc.unisys.com      (215) 648-7256
Dept of Comp & Info Science, U of PA  lang@linc.cis.upenn.edu  (215) 898-9511

bin@primate.wisc.edu (Brain in Neutral) (10/03/89)

From article <11628@burdvax.PRC.Unisys.COM>, by lang@PRC.Unisys.COM (Francois-Michel Lang):
> No need to write a script.
> tail -r does this already.

If your tail has -r, that is.  Not all do.

cpcahil@virtech.UUCP (Conor P. Cahill) (10/04/89)

In article <MONTNARO.89Oct2224215@sprite.crd.ge.com>, montnaro@sprite.crd.ge.com (Skip Montanaro) writes:
> Does somebody have an elegant shell script for reversing the lines of a
> file? I've come up with the following short one:

How about the following pipeline:

grep -n "\$" t.c | sort -rn | sed "s/^[0-9]*://"

what this does is as follows:

	grep 	- get all lines of the file and number them
	sort	- key is numeric and sort in reverse order
	sed	- remove line numbers added by grep

Good luck.

-- 
+-----------------------------------------------------------------------+
| Conor P. Cahill     uunet!virtech!cpcahil      	703-430-9247	!
| Virtual Technologies Inc.,    P. O. Box 876,   Sterling, VA 22170     |
+-----------------------------------------------------------------------+

cpcahil@virtech.UUCP (Conor P. Cahill) (10/04/89)

In response to an article about reversing a file...

In article <1989Oct3.041122.28028@ctr.columbia.edu>, seth@ctr.columbia.edu (Seth Robertson) writes:
> 			   ***************
> 			   ***************
> 		           **           **
> 			   **  tail -r  **
> 		           **           **
> 			   ***************
> 			   ***************

This wont work on system V (since -r is not an option to tail) and probably 
wont work on most other unixes if the file is large since tail will only read
the last block (not sure of exact size) of the file.

This is the problem when you want to get the last 1000 lines in a file of 
80 character lines.  I haven't found a standard tail that will properly 
handle this, but I have written one.

-- 
+-----------------------------------------------------------------------+
| Conor P. Cahill     uunet!virtech!cpcahil      	703-430-9247	!
| Virtual Technologies Inc.,    P. O. Box 876,   Sterling, VA 22170     |
+-----------------------------------------------------------------------+

itkin@mrspoc.Transact.COM (Steven M. List) (10/04/89)

montnaro@sprite.crd.ge.com (Skip Montanaro) writes:

>Does somebody have an elegant shell script for reversing the lines of a
>file? I've come up with the following short one:

This uses one of my all-time favorite VI/EX commands, and this is the
first time I can remember anyone ASKING for it:

	echo "g/./.m0\nw $OUTPUT\nq" | ex $INPUT

the "g/./.m0" marks every line in the file and then moves each marked
line to the beginning of the file (after line zero).  The "w $OUTPUT"
will either write the reversed file to a new file or overwrite the
original file, depending on whether or not OUTPUT is valued.

For those of us who DON'T have "tail -r", this works great!  From within
VI, you can use the same global command:

	:g/./.m0

-- 
 +----------------------------------------------------------------------------+
 :                Steven List @ Transact Software, Inc. :^>~                  :
 :           Chairman, Unify User Group of Northern California                :
 :     {apple,coherent,limbo,mips,pyramid,ubvax}!itkin@guinan.Transact.COM    :

dts@quad.uucp (David T. Sandberg) (10/04/89)

In article <1989Oct3.041122.28028@ctr.columbia.edu> seth@ctr.columbia.edu (Seth Robertson) writes:
:In article <MONTNARO.89Oct2224215@sprite.crd.ge.com> <montanaro@crdgw1.ge.com> (Skip Montanaro) writes:
:>Does somebody have an elegant shell script for reversing the lines of a
:>file?
:
: tail -r

On what system?  Tail doesn't have an -r switch on any of the Sys V
machines I have access to.  Besides, based on the default behavior
of tail, this would only affect the last ten lines.  (of course, the
mythical "-r" flag could alter that behavior, I guess)

>Anything else you want?

How about something everyone can make use of?

-- 
                                  David Sandberg - Quadric Systems
 "I began neglecting my shoes."   PSEUDO: dts@quad.uucp
                                  ACTUAL: ..uunet!rosevax!sialis!quad!dts

dlp@gistdev.UUCP (Dirk Pellett) (10/04/89)

>montnaro@sprite.crd.ge.com (Skip Montanaro) writes:
>>Does somebody have an elegant shell script for reversing the lines of a
>>file? I've come up with the following short one:

itkin@mrspoc.Transact.COM (Steven M. List) replies:
>This uses one of my all-time favorite VI/EX commands, and this is the
>first time I can remember anyone ASKING for it:
>	echo "g/./.m0\nw $OUTPUT\nq" | ex $INPUT
>the "g/./.m0" marks every line in the file and then moves each marked
>line to the beginning of the file (after line zero).

Actually, it marks all lines except blank lines.  What you really want is
the following:
        echo 'g/^/m0
        w
        q' | ed $1
That way you won't end up will a ton of blank lines at the end of your file.
-- 
-- 
Dirk Pellett			uunet!gistdev!dlp

davr@hrtix.UUCP (David C. Raines) (10/04/89)

In article <MONTNARO.89Oct2224215@sprite.crd.ge.com>, montnaro@sprite.crd.ge.com (Skip Montanaro) writes:
> Does somebody have an elegant shell script for reversing the lines of a
> file? I've come up with the following short one:

Awk version:

{
	array[NR] = $0
}
END {
	for (i = NR; i > 0; i--)
		print array[i]
}
-- 
David Raines			TCA  5 National Dr., Windsor Locks, CT 06096
UUCP:  ...!uunet!hrtix!davr		

jak@sactoh0.UUCP (Jay A. Konigsberg) (10/05/89)

In article <MONTNARO.89Oct2224215@sprite.crd.ge.com> <montanaro@crdgw1.ge.com> (Skip Montanaro) writes:
>>Does somebody have an elegant shell script for reversing the lines of a
>>file? I've come up with the following short one:

><Recursive code deleted>

>Ready?
>			   **  tail -r  **

>Anything else you want?

This is a nice, clean way to do it, though " tail " had (has?) a
bug (feature?) relating to file size. It can't (won't) create a
file larger than 512 blocks. Depending on the size of the original
file, it could create a problem.

-- 
#############################################################
#  Jay Konigsberg	     # (916) 484-6029		    #
#  SAC-UNIX, Sacramento, Ca. # UUCP=...pacbell!sactoh0!jak  #
#############################################################

merlyn@iwarp.intel.com (Randal Schwartz) (10/05/89)

In article <2283@munnari.oz.au>, ok@cs (Richard O'Keefe) writes:
| In article <MONTNARO.89Oct2224215@sprite.crd.ge.com>, montnaro@sprite.crd.ge.com (Skip Montanaro) writes:
| >Does somebody have an elegant shell script for reversing the lines of a file?
| In BSD systems:
| 	cat -n $* | sort -nr | sed -e 's/^.......//'
| In System V:
| 	pr -t -n6 $* | sort -nr | cut -f2-

In Perl, of course, it's

perl -e 'unshift(a,$_) while (<STDIN>); print @a;'

which works even when 'tail -r' (another solution in another post)
doesn't, provided you have the real/virtual memory to spare.

Just another Perl hacker,
-- 
/== Randal L. Schwartz, Stonehenge Consulting Services (503)777-0095 ====\
| on contract to Intel's iWarp project, Hillsboro, Oregon, USA, Sol III  |
| merlyn@iwarp.intel.com ...!uunet!iwarp.intel.com!merlyn	         |
\== Cute Quote: "Welcome to Oregon... Home of the California Raisins!" ==/

tanner@cdis-1.uucp (Dr. T. Andrews) (10/05/89)

itkin@mrspoc.Transact.COM (Steven M. List) writes:
) 	echo "g/./.m0\nw $OUTPUT\nq" | ex $INPUT
) 	:g/./.m0
Very clever, but does not deal effectively with zero-lentgh lines.
In "vi", try the following (similar change for echo command above)
	:g/^/.m0
-- 
He cuts half of passenger service | {bpa,uunet}!cdin-1!cdis-1!tanner
Mulroney: "cold froze our brains" | {attctc gatech!uflorida}!ki4pv!cdis-1!tanner

henseler@uniol.UUCP (Herwig Henseler) (10/06/89)

montnaro@sprite.crd.ge.com (Skip Montanaro) writes:
> Does somebody have an elegant shell script for reversing the lines of a
> file?

The solution in perl:

@file = <>;
$i = $#file + 1;
print $file[$i]  while $i--;

	bye, Herwig
--
## Herwig Henseler (CS-Student) D-2930 Varel, Tweehoernweg 69 | Brain fault- ##
## EMail: henseler@uniol.UUCP (..!uunet!unido!uniol!henseler) | core dumped  ##

merlyn@iwarp.intel.com (Randal Schwartz) (10/06/89)

In article <1989Oct3.201759.19182@mrspoc.Transact.COM>, itkin@mrspoc (Steven M. List) writes:
| montnaro@sprite.crd.ge.com (Skip Montanaro) writes:
| 
| >Does somebody have an elegant shell script for reversing the lines of a
| >file? I've come up with the following short one:
| 
| This uses one of my all-time favorite VI/EX commands, and this is the
| first time I can remember anyone ASKING for it:
| 
| 	echo "g/./.m0\nw $OUTPUT\nq" | ex $INPUT
| 
| the "g/./.m0" marks every line in the file and then moves each marked
| line to the beginning of the file (after line zero).  The "w $OUTPUT"
| will either write the reversed file to a new file or overwrite the
| original file, depending on whether or not OUTPUT is valued.
| 
| For those of us who DON'T have "tail -r", this works great!  From within
| VI, you can use the same global command:
| 
| 	:g/./.m0
| 

Arrgh.  Both of those fail on *blank* lines.  (They'll all end up at
either the beginning or the end... I'm too tired to figure out which.)

Try:

   echo "g/^/m0|w $OUTPUT|q" | ex $INPUT

instead.

Just another 'ex' hacker,
-- 
/== Randal L. Schwartz, Stonehenge Consulting Services (503)777-0095 ====\
| on contract to Intel's iWarp project, Hillsboro, Oregon, USA, Sol III  |
| merlyn@iwarp.intel.com ...!uunet!iwarp.intel.com!merlyn	         |
\== Cute Quote: "Welcome to Oregon... Home of the California Raisins!" ==/

bush%ecs.oxford.ac.uk@nsfnet-relay.ac.uk (Mark Bush) (10/07/89)

> Does somebody have an elegant shell script for reversing the lines of a
> file? I've come up with the following short one:

For all perl fans:

while (<>)
{
    $line{$.} = $_;
}

for ($i = $.; $i > 0; $i--)
{
    print $line{$i};
}

Mark Bush                      bush%uk.ac.oxford.prg@ac.uk
Teaching Support Programmer    bush%prg.oxford.ac.uk@nsfnet-relay.ac.uk
OUCL                           ...!uunet!mcvax!ukc!ox-prg!bush

jon@jonlab.UUCP (Jon LaBadie) (10/08/89)

A posting asked about how to reverse the sequence of the lines of a
file.  Generally, my news feed is delayed by 3 - 5 days so I do not
post responses to the net, but mail to the poster directly.  I assume
the answer will be provided by someone else before my posting would
ever make it to the net.

However, in this case, no one seems to have posted the (IMHO) elegant
solution I would propose.  Thus, my suggestion for:

	HOW DO YOU REVERSE THE LINES OF A FILE?

ed - ${1} <<!
    g/^/m0
    w
    q
!

Any questions?

-- 
Jon LaBadie
{att, princeton, bcr}!jonlab!jon
{att, attmail, bcr}!auxnj!jon

ok@cs.mu.oz.au (Richard O'Keefe) (10/08/89)

In article <810@jonlab.UUCP>, jon@jonlab.UUCP (Jon LaBadie) writes:
> However, in this case, no one seems to have posted the (IMHO) elegant
> solution I would propose.  Thus, my suggestion for:
> 	HOW DO YOU REVERSE THE LINES OF A FILE?
> ed - ${1} <<!
>     g/^/m0
>     w
>     q
> !
> Any questions?

No questions, but several comments.
(a) This method, like most others that have been posted, assumes that
    your virtual memory is at least as big as your file.  That's not
    really an elegant assumption.  (I occasionally handled 1/2M files
    on a PDP-11 with a massive 64k of virtual memory -- no separate I/D.)
    It's a particularly bad assumption when you bring ed(1) into it,
    because ed tends to live in the past (be compiled with limits
    appropriate to a PDP-11 rather than an 80386).  For example:
	ed may strip off the 8th bit of characters
	ed may truncate lines to 512 characters
	ed may limit its "work file" to 64k or 128k characters
	ed may not handle long file names (64 character limit sometimes)
	ed may just plain not work
    All of these have hit me in real UNIX releases as provided by vendors.
    (Not all at the same time.)

(b) This method requires the text to already be in a file; it can't be
    used with a pipe.  (A temporary file can be used, but do remember to
    try $TMPDIR rather than assuming /tmp and do remember to rm it.)

(c) The reversed lines are written back onto the original file.  That's
    not necessarily a good idea.  The file might be write protected.
    The intended user might not want that.

[d: strange things will happen if any input lines contain NULs, but that
    also applies to my solution using sort(1), and to any UNIX utility
    that reads its input with fgets().]

In general, The UNIX Way of doing something like this is to make it look
as much like a filter as possible.

don@dgbt.uucp (Donald McLachlan) (10/09/89)

	If you wnat to reverse the lines from right to left, try the
command 'rev filename'.

yahoo@unix.cis.pitt.edu (Kenneth L Moore) (10/13/89)

In article <1260@dgbt.uucp> don@dgbt.uucp (Donald McLachlan) writes:

>	If you wnat to reverse the lines from right to left, try the
>command 'rev filename'.

That's a new one. Thanks.

Ken

-- 
ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken
ken ken ken ken kne ken ken ken ken ken ken ken ken ken ken ken ken ken ken
ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken
ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken ken

jpr@dasys1.UUCP (Jean-Pierre Radley) (10/13/89)

In article <MONTNARO.89Oct2224215@sprite.crd.ge.com> <montanaro@crdgw1.ge.com> (Skip Montanaro) writes:
>Does somebody have an elegant shell script for reversing the lines of a
>file? I've come up with the following short one:


I don't think I made this up. Maybe I got it from Kernighan & Pike.
But it does seem simpler than the answers I've seen posted to date,
if you don't have 'tail -r', and if the file isn't too big (whatever that
may be, but at some point awk runs out of space).

<file awk '
	{ line[i++] = $0 }
END	{ while (i--) print line[i] }
'
-- 
Jean-Pierre Radley					      jpr@jpradley.uucp
New York, NY					      72160.1341@compuserve.com

jc@minya.UUCP (John Chambers) (10/14/89)

In article <MONTNARO.89Oct2224215@sprite.crd.ge.com>, montnaro@sprite.crd.ge.com (Skip Montanaro) writes:
> Does somebody have an elegant shell script for reversing the lines of a
> file? I've come up with the following short one:
	... recursive example deleted ...
> It has two obvious disadvantages. First, it won't work for very long files,
> since it uses Unix processes to simulate a stack of lines.  Second, the
> Bourne shell's builtin read command doesn't preserve interword white space,
> separating words by the value of the IFS environment variable instead.

Here's the fastest way I can think of without stooping to writing in C:
	for f
	do	ed - $f <<'EOF'
	g/^/m0
	w
	q
	EOF
	done
This takes a list of filenames, and reverses each of them.  This might
not be, strictly speaking, an answer to your question, since it uses
ed to do the work, and so it isn't really done by the script.  It also
has a minor bug (unwanted output) in the boundary case of a null file.
But it works for files as large as ed can handle.

It's curious that when I used "1,$" instead of "g/^/", it didn't work.
I wonder if this is a general ed failing, or if it's just here.


-- 
#echo 'Opinions Copyright 1989 by John Chambers; for licensing information contact:'
echo '	John Chambers <{adelie,ima,mit-eddie}!minya!{jc,root}> (617/484-6393)'
echo ''
saying

jc@minya.UUCP (John Chambers) (10/14/89)

In article <1989Oct3.201759.19182@mrspoc.Transact.COM>, itkin@mrspoc.Transact.COM (Steven M. List) writes:
> montnaro@sprite.crd.ge.com (Skip Montanaro) writes:
> >Does somebody have an elegant shell script for reversing the lines of a
> >file? I've come up with the following short one:
> 
> This uses one of my all-time favorite VI/EX commands, and this is the
> first time I can remember anyone ASKING for it:
> 	echo "g/./.m0\nw $OUTPUT\nq" | ex $INPUT
> the "g/./.m0" marks every line in the file and then moves each marked
> line to the beginning of the file (after line zero).  

No, it doesn't.  You didn't test it against a file containing null lines.
If you had, you would have discovered that the "g/./" only matches lines
with characters in them.  So what you get is the non-null lines in reverse
order, followed by as many null lines as were in the original file.  Try:
 	echo "g/^/m0\nw $OUTPUT\nq" | ex $INPUT
Note also that the second dot, while not wrong, is not needed.  

(I guess you flunk your ex-wizard test.  Now if I could only figure out
how to type map commands to vi so that it does something useful, rather
than giving me error messages.  ;-)

>						The "w $OUTPUT"
> will either write the reversed file to a new file or overwrite the
> original file, depending on whether or not OUTPUT is valued.

That's clever.

-- 
#echo 'Opinions Copyright 1989 by John Chambers; for licensing information contact:'
echo '	John Chambers <{adelie,ima,mit-eddie}!minya!{jc,root}> (617/484-6393)'
echo ''
saying

tony@oha.UUCP (Tony Olekshy) (10/14/89)

Hmm, in the interest of further promoting perl:

@L = <stdin>; print pop(@L) while $#L+1;	# Read FIFO, Write LIFO!

--
Yours, etc., Tony Olekshy (...!alberta!oha!tony or tony@oha.UUCP).

chris@mimsy.UUCP (Chris Torek) (10/14/89)

In article <39@minya.UUCP> jc@minya.UUCP (John Chambers) writes:
>... g/^/m0 ...
>It's curious that when I used "1,$" instead of "g/^/", it didn't work.
>I wonder if this is a general ed failing, or if it's just here.

It is a feature.

`g/pat/cmd' applies `cmd' to each line matching `pat'---meaning each
line is examined one at a time, and if it matches, `cmd' is done.
`1,$cmd', however, applies `cmd' to the range 1,$.

It should become obvious what is going on:  The first command moves line
1 after line 0, then (what was, and here still is) line 2 after line 0,
and then moves line 3 after 0, and so forth.  The second moves all the
lines, as a single unit, to after line 0.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@cs.umd.edu	Path:	uunet!mimsy!chris