tac (was Re: shell pipeline to reverse the order of lines.)

Jonathan I. Kamens jik at athena.mit.edu
Mon Feb 18 09:02:43 AEST 1991


Submitted by: jik at ATHENA.MIT.EDU
Archive-name: tac

  Dan's version is good, but it's unfair to point out that it's faster than
tac without also pointing out that tac is much more powerful.  Tac allows the
reversal of a file based on an arbitrary string as the chunk separator, rather
than only allowing newline to be used as the chunk separator.

  Therefore, for example, you could use the string "\nFrom " as a separator
and display your mail file, most recent messages first.

  There's also the gnu version of tac, which (I believe) allows separation
based on regular expressions instead of just based on strings.  It's available
as part of the "file" utilities at better GNU archive sites in your
neighborhood.

  Below is the original version of tac, which is freely redistributable (I've
checked with the author about this; he said, "I think nowadays I'd add the std
`Copymiddle' that CSRG puts on, Utah-ized.").  The author (Jay Lepreau,
lepreau%mancos at cs.utah.edu) says that it will probably be part of 4.4BSD in
some manner.

  Send bug reports to the author, not me :-).

-- 
Jonathan Kamens			              USnail:
MIT Project Athena				11 Ashford Terrace
jik at Athena.MIT.EDU				Allston, MA  02134
Office: 617-253-8085			      Home: 617-782-0710

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  README Makefile tac.1 tac.c tmail.1 tmail.sh
# Wrapped by jik at pit-manager on Sun Feb 17 17:01:17 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(1013 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
XTitle:		tac
X
XAuthor: 	Unknown off the net long ago, and
X		Jay Lepreau
X		Computer Science Dept.
X		Univ. of Utah
X		Salt Lake City, UT 84112
X		801-581-4285
X	
XNet:		lepreau at utah-cs.arpa, {ihnp4,decvax}!utah-cs!lepreau
X		Sometime lepreau at cs.utah.edu
X
Xtac ("cat" backwards) is a small program which prints file segments
Xin reverse order; by default it reverses by lines (like tail -r).
XBesides being able to reverse a file by segments delimited by an
Xarbitrary string, its wins are that it is very fast, handles any
Xsize segments and any size files.  These make it an excellent
Xfilter, e.g. "tac <logfile> | egrep foo".
X
Xtmail is a trivial shell script which uses tac to display one's
Xmailbox, message by message, most recent first.
X
XPotential improvements to tac which I would appreciate having fed back to me:
X--segment by arbitrary regular expressions, but only if it's done
X	w/o sacrificing the current speed for the default case.
X--fix up handling of multiple files.
X--nicely integrate with tail somehow.
X
X1/11/86
END_OF_FILE
if test 1013 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(1175 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X#	Makefile	1.1	6/9/85
X#
XDESTDIR=
XBINDIR=	/usr/new
XMANTYPE= n
X
XCFLAGS=	-O
X
XSTD=	tac
XSCRIPT=	tmail
XMAN=	tac.1 tmail.1
X
Xall:	${STD}
X
X${STD}:
X	cc ${CFLAGS} -o $@ $@.c
X
Xinstall: all
X	install -s tac ${DESTDIR}${BINDIR}/tac
X	install -m 755 -c tmail.sh ${DESTDIR}${BINDIR}/tmail
X	-for i in ${MAN}; do \
X	    (install -m 644 -c $$i \
X	    ${DESTDIR}/usr/man/man${MANTYPE}/`basename $$i .1`.${MANTYPE}); \
X	    done
X
Xclean:
X	rm -f *.o ${STD} Makefile.bak makedep eddep a.out core errs 
X
Xdepend:
X	rm -f makedep
X	for i in ${STD}; do \
X		(${CC} -M $$i.c |sed 's/.o: /: /' >>makedep); done
X	echo '/^# DO NOT DELETE THIS LINE/+2,$$d' >eddep
X	echo '$$r makedep' >>eddep
X	echo 'w' >>eddep
X	chmod u+w Makefile
X	cp Makefile Makefile.bak
X	ed - Makefile < eddep
X	rm -f eddep makedep
X	echo '# DEPENDENCIES MUST END AT END OF FILE' >> Makefile
X	echo '# IF YOU PUT STUFF HERE IT WILL GO AWAY' >> Makefile
X	echo '# see make depend above' >> Makefile
X
X# DO NOT DELETE THIS LINE -- make depend uses it
X
Xtac: tac.c
Xtac: /usr/include/sys/types.h
Xtac: /usr/include/sys/stat.h
Xtac: /usr/include/stdio.h
X# DEPENDENCIES MUST END AT END OF FILE
X# IF YOU PUT STUFF HERE IT WILL GO AWAY
X# see make depend above
END_OF_FILE
if test 1175 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'tac.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'tac.1'\"
else
echo shar: Extracting \"'tac.1'\" \(1307 characters\)
sed "s/^X//" >'tac.1' <<'END_OF_FILE'
X.\"	@(#)tac.1	1.2 6/5/86
X.\"
X.TH TAC 1 "June 5, 1986"
X.SH NAME
Xtac \- concatenate and print files in reverse
X.SH SYNOPSIS
X.B tac
X[
X.B \-string
X] [
X.B +string
X] [ file ... ]
X.SH DESCRIPTION
X.I Tac
Xreads each
X.I file
Xin sequence
Xand writes it on the standard output, reversed by the file segments
Xdelimited by
X.I string.
X.I \-string
Xspecifies segments bounded on the left by
X.I string,
Xwhile
X.I +string
Xspecifies right-bounded segments.
XThe default is
X.I +\en
X(print lines in reverse order).
XIf no input
X.I file
Xis given, or if the argument `-'
Xis encountered,
X.I tac
Xreads from the standard input.  Note that in this case
X.I tac
Xstores the entire standard input in a temporary
Xfile before it outputs anything, so for large input it is slow.
X.SH EXAMPLES
X.RS
Xtac '-\e
X.br
XFrom\ ' /usr/spool/mail/$USER
X.RE
Xprints out one's mail messages, most recent first.
X.PP
X.RS
Xtac file
X.RE
Xprints the file in reverse, line by line, and:
X.PP
X.RS
Xtac /usr/adm/messages | egrep 'hp.*hard'
X.RE
Xprints out the hard errors on MASSBUS disk drives, most recent first.
X.SH SEE ALSO
Xcat(1), rev(1), tail(1), tmail(1)
X.SH BUGS
X.I Tac
Xdoesn't handle multiple argument files exactly right, and it's
Xalso unclear in which order they should be processed.
X.br
XIf invoked as `tac < file', 
X.I tac
Xuses a temp file but it doesn't have to.
END_OF_FILE
if test 1307 -ne `wc -c <'tac.1'`; then
    echo shar: \"'tac.1'\" unpacked with wrong size!
fi
# end of 'tac.1'
fi
if test -f 'tac.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'tac.c'\"
else
echo shar: Extracting \"'tac.c'\" \(5902 characters\)
sed "s/^X//" >'tac.c' <<'END_OF_FILE'
X#ifndef lint
Xstatic char sccsid[] = "@(#)tac.c	1.4 6/5/86";
X#endif
X
X/*
X * tac.c - Print file segments in reverse order
X *
X * Original line-only version by unknown author off the net.
X * Rewritten in 1985 by Jay Lepreau, Univ of Utah, to allocate memory
X * dynamically, handle string bounded segments (suggested by Rob Pike),
X * and handle pipes.
X */
X
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <stdio.h>
X#include <signal.h>
X
X/*
X * This should be defined for BSD releases later than 4.2 and for Sys V.2,
X * at least.  fwrite is faster than putc only if you have a new speedy fwrite.
X */
X#define FAST_FWRITE
X
X#ifdef DEBUG				/* dbx can't handle registers */
X#include <ctype.h>
X# define register
X#endif
X
X/* Default target string and bound */
Xint right = 1;				/* right or left bounded segments? */
Xchar *targ = "\n";
X
Xchar *tfile;
Xchar *buf;
X
Xint readsize = 4096;			/* significantly faster than 1024 */
Xint bufsize;
Xint targlen;
Xint numerr;
X
Xint cleanup();
Xextern off_t lseek();
Xextern char *strcpy(), *malloc(), *realloc(), *mktemp();
X
Xmain(argc, argv)
X    int argc;
X    char **argv;
X{
X
X#ifdef DEBUG
X    if (argc > 1 && isdigit(*argv[1])) {
X	readsize = atoi(argv[1]);
X	argc--, argv++;
X    }
X#endif
X    
X    if (argc > 1 && (argv[1][0] == '+' || argv[1][0] == '-') && argv[1][1]) {
X	targ = &argv[1][1];
X	right = (argv[1][0] == '+');
X	argc--; argv++;
X    }
X    targlen = strlen(targ);
X
X    bufsize = (readsize << 1) + targlen + 2;
X    if ((buf = malloc((unsigned) bufsize)) == NULL) {
X	perror("tac: initial malloc");
X	exit(1);
X    }
X
X    (void) strcpy(buf, targ);		/* stop string at beginning */
X    buf += targlen;
X
X    if (argc == 1)
X	tacstdin();
X    while (--argc) {
X	if (strcmp(*++argv, "-") == 0)
X	    tacstdin();
X	else
X	    tacit(*argv);
X    }
X    exit(numerr > 0 ? 1 : 0);
X}
X
Xtacstdin()
X{
X
X    int (*sigint)(), (*sighup)(), (*sigterm)();
X
X    if ((sigint = signal(SIGINT, SIG_IGN)) != SIG_IGN)
X	(void) signal(SIGINT, cleanup);
X    if ((sighup = signal(SIGHUP, SIG_IGN)) != SIG_IGN)
X	(void) signal(SIGHUP, cleanup);
X    if ((sigterm = signal(SIGTERM, SIG_IGN)) != SIG_IGN)
X	(void) signal(SIGTERM, cleanup);
X
X    savestdin();
X    tacit(tfile);
X    (void) unlink(tfile);
X
X    (void) signal(SIGINT, sigint);
X    (void) signal(SIGHUP, sighup);
X    (void) signal(SIGTERM, sigterm);
X}
X
Xchar template[] = "/tmp/tacXXXXXX";
Xchar workplate[sizeof template];
X
Xsavestdin()
X{
X    int fd;
X    register int n;
X
X    (void) strcpy(workplate, template);
X    tfile = mktemp(workplate);
X    if ((fd = creat(tfile, 0600)) < 0) {
X	prterr(tfile);
X	cleanup();
X    }
X    while ((n = read(0, buf, readsize)) > 0)
X	if (write(fd, buf, n) != n) {
X	    prterr(tfile);
X	    cleanup();
X	}
X    (void) close(fd);
X    if (n < 0) {
X	prterr("stdin read");
X	cleanup();
X    }
X}
X
Xtacit(name)
X    char *name;
X{
X    register char *p, *pastend;
X    register int firstchar, targm1len;	/* target length minus 1 */
X    struct stat st;
X    off_t off;
X    int fd, i;
X
X    firstchar = *targ;
X    targm1len = targlen - 1;
X
X    if (stat(name, &st) < 0) {
X	prterr(name);
X	numerr++;
X	return;
X    }
X    if ((off = st.st_size) == 0)
X	return;
X    if ((fd = open(name, 0)) < 0) {
X	prterr(name);
X	numerr++;
X	return;
X    }
X
X    /*
X     * Arrange for the first read to lop off enough to
X     * leave the rest of the file a multiple of readsize.
X     * Since readsize can change, this may not always hold during
X     * the pgm run, but since it usually will, leave it here
X     * for i/o efficiency (page/sector boundaries and all that).
X     * Note: the efficiency gain has not been verified.
X     */
X    if ((i = off % readsize) == 0)
X	i = readsize;
X    off -= i;
X
X    (void) lseek(fd, off, 0);
X    if (read(fd, buf, i) != i) {
X	prterr(name);
X	(void) close(fd);
X	numerr++;
X	return;
X    }
X    p = pastend = buf + i;		/* pastend always points to end+1 */
X    p -= targm1len;
X
X    for (;;) {
X	while ( *--p != firstchar ||
X	  (targm1len && strncmp(p+1, targ+1, targm1len)) )
X	    continue;
X	if (p < buf) {		/* backed off front of buffer */
X	    if (off == 0) {
X		/* beginning of file: dump last segment */
X		output(p + targlen, pastend);
X		(void) close(fd);
X		break;
X	    }
X	    if ((i = pastend - buf) > readsize) {
X		char *tbuf;
X		int newbufsize = (readsize << 2) + targlen + 2;
X		
X		if ((tbuf = realloc(buf-targlen, (unsigned) newbufsize)) == NULL) {
X		    /* If realloc fails, old buf contents may be lost. */
X		    perror("tac: segment too long; may have garbage here");
X		    numerr++;
X		    i = readsize;
X		}
X		else {
X		    tbuf += targlen;	/* skip over the stop string */
X		    p += tbuf - buf;
X		    pastend += tbuf - buf;
X		    buf = tbuf;
X		    bufsize = newbufsize;
X		    readsize = readsize << 1;
X		    /* guaranteed to fit now (I think!) */
X		}
X	    }
X	    if (off - readsize < 0) {
X		readsize = off;
X		off = 0;
X	    }
X	    else
X		off -= readsize;
X	    (void) lseek(fd, off, 0);	/* back up */
X	    /* Shift pending old data right to make room for new */
X	    bcopy(buf, p = buf + readsize, i);
X	    pastend = p + i;
X	    if (read(fd, buf, readsize) != readsize) {
X		prterr(name);
X		numerr++;
X		(void) close(fd);
X		break;
X	    }
X	    continue;
X	}
X	/* Found a real instance of the target string */
X	output(right ? p + targlen : p, pastend);
X	pastend = p;
X	p -= targm1len;
X    }
X}
X
X/*
X * Dump chars from p to pastend-1.  If right-bounded by target
X * and not the first time through, append the target string.
X */
Xoutput(p, pastend)
X    register char *p;
X    char *pastend;
X{
X    static short first = 1;
X
X#ifdef FAST_FWRITE
X    (void) fwrite(p, 1, pastend - p, stdout);
X#else
X    while (p < pastend)
X	(void) putc(*p++, stdout);
X#endif
X    if (right && !first)
X	(void) fwrite(targ, 1, targlen, stdout);
X    first = 0;
X    if ferror(stdout) {
X	perror("tac: fwrite/putc");
X	exit(++numerr > 1 ? numerr : 1);
X    }
X}
X
Xprterr(s)
X    char *s;
X{
X
X    fprintf(stderr, "tac: ");
X    perror(s);
X}
X
Xcleanup()
X{
X
X    (void) unlink(tfile);
X    exit(1);
X}
END_OF_FILE
if test 5902 -ne `wc -c <'tac.c'`; then
    echo shar: \"'tac.c'\" unpacked with wrong size!
fi
# end of 'tac.c'
fi
if test -f 'tmail.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'tmail.1'\"
else
echo shar: Extracting \"'tmail.1'\" \(592 characters\)
sed "s/^X//" >'tmail.1' <<'END_OF_FILE'
X.\"	@(#)tmail.1	1.1 6/9/85
X.\"
X.TH TMAIL 1 "June 9, 1985"
X.SH NAME
Xtmail \- print out mail messages, most recent first
X.SH SYNOPSIS
X.B tmail
X[ username ]  [ mboxfile ]
X.SH DESCRIPTION
X.I Tmail
Xprints Unix-style mail messages in reverse order (most recent first).
XIf no argument is given it looks in your system maildrop
X.RI ( /usr/spool/mail/$USER ).
XAn argument which is a valid
X.I username
Xcauses
X.I tmail
Xto look in that person's maildrop;
Xotherwise the argument should be the name of a Unix-style 
X``mailbox'' file.
X.SH SEE ALSO
Xtac(1), cat(1).
X.SH BUGS
XShould handle multiple arguments.
END_OF_FILE
if test 592 -ne `wc -c <'tmail.1'`; then
    echo shar: \"'tmail.1'\" unpacked with wrong size!
fi
# end of 'tmail.1'
fi
if test -f 'tmail.sh' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'tmail.sh'\"
else
echo shar: Extracting \"'tmail.sh'\" \(402 characters\)
sed "s/^X//" >'tmail.sh' <<'END_OF_FILE'
X#! /bin/sh
X#	@(#)tmail.sh	1.2 10/17/85
X# Print out mail backwards.
X# Author: Jay Lepreau, Univ of Utah
X#
XPATH=/usr/new:/bin:/usr/bin:/usr/ucb
Xcase $# in
X	0) file=/usr/spool/mail/$USER
X	   ;;
X	1) if [ -r /usr/spool/mail/$1 ]
X	   then
X		file=/usr/spool/mail/$1
X	   else
X	   	file=$1
X	   fi
X	   ;;
X	*) echo "Usage: `basename $0` [ username ] [ mboxfile ]"
X	   exit 1
X	   ;;
Xesac	
Xexec tac '-
XFrom ' $file
END_OF_FILE
if test 402 -ne `wc -c <'tmail.sh'`; then
    echo shar: \"'tmail.sh'\" unpacked with wrong size!
fi
# end of 'tmail.sh'
fi
echo shar: End of shell archive.
exit 0



More information about the Alt.sources mailing list