jik@athena.mit.edu (Jonathan I. Kamens) (02/18/91)
Submitted by: jik@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@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@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@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@utah-cs.arpa, {ihnp4,decvax}!utah-cs!lepreau X Sometime lepreau@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