[net.sources] GREP-like command with context printing...

dan@rna.UUCP (Dan Ts'o) (04/24/86)

x
	I've had a number of requests for this program so here it is, G.
G functions almost identical to grep except that it has a -c# option
to print # lines around a match. G, however, only understands the
^.$ regular expression characters.
	G also has options to ignore case, find matches for more than
one regular expression, and most of grep's options. G also has an alterego,
TELE, which provides a convenient telephone-file searcher. G compiles
under Microsoft C 3.0 and works fine under MSDOS as well as UNIX.

					Cheers,
					Dan Ts'o
					Dept. Neurobiology
					Rockefeller Univ.
					1230 York Ave.
					NY, NY 10021
					212-570-7671
					...cmcl2!rna!dan
					rna!dan@cmcl2.arpa

echo tele.1
sed 's/^X//' > tele.1 << 'All work and no play makes Jack a dull boy'
X.ds ]W Rockefeller Neurobiology
X.TH TELE 1 8/6/84
X.SH NAME
Xtele,g  \-  find telephone numbers/alternative grep command
X.SH SYNOPSIS
X.B tele
X[
X.B \-abcfghlnpstvCFN
X]\ \ names or patterns ...
X.br
X.B g
X[
X.B \-abcfghlnpstvCFN
X]\ \ pattern\ \ [
Xfilename ...
X]
X.SH DESCRIPTION
XThis commands has two modes of function, \fItele\fP and \fIg\fP, as
Xset by the name of invocation.
X.I Tele
Xfinds lines contain a match to given names or patterns in a group of
Xfiles and prints the match lines on the standard output. Searches are
Xusually case folded in this mode.
XThe following files are searched in the given order:
X.sp
X.nf
X.in +1i
X$PHONE
X$HOME/phone
X$HOME/Phone
X/usr/pub/phone
X/usr/pub/phone.local
X/usr/pub/phone.misc
X.in -1i
X.fi
X.sp
XThe
X.I g
Xentry accepts a \fIgrep\fP-like command syntax and only searches among the
Xgroup of files given on the command line, or the standard input.
XBoth modes accept the regular expression operators ^ . $ which match the
Xbeginning of a line, any character, and the end of a line, respectively.
XNo other regular expression operators are recognized.
XThe options are given below.
X.TP
X.BI \-c [#lines]
XPrints a number of lines surrounding a match, providing a context of the
Xmatch. If a number of lines of context is not given, one line before and one
Xline after a match are printed.
X.TP
X.BI \-a [#lines]
X.TP
X.BI \-b [#lines]
X.br
XSimilar to
X.B \-c
Xexcept that specified number of lines are printed only after or before the
Xmatching line, respectively.
X.TP
X.B \-p
XModifies the number of context lines printed after a match (as specified
Xby
X.B \-c
Xand
X.B \-a
X) to extend to the next empty line. This is useful for printing to the end
Xof a paragraph or text section.
X.TP
X.BI \-h [#hits]
XOnly print up to the given number of matches. If no number is given,
Xthe command terminates after printing one matching line.
X.TP
X.B \-l
XOnly print filenames that contain matches, one filename per line.
X.TP
X.BI \-e pattern
XAdd the given pattern to the list of patterns to be searched for. Only
Xfunctional with \fIg\fP mode.
X.TP
X.B \-v
XMatch lines which do not contain the pattern instead of lines that do
Xcontain the pattern.
X.TP
X.B \-n
XPrint the line numbers of matching lines as well.
X.TP
X.B \-N
XPrint the filename of matching lines as well. This is the default behavior
Xfor \fIg\fP mode with multiple input files.
X.TP
X.B \-C
XPrint only the count of matches.
X.TP
X.B \-s
XSuppress matching line output and error messages about unopenable files.
XUseful for testing just the exit value for success or failure.
X.TP
X.B "\-f,-i"
XFold upper and lower case when performing matches. This is the default
Xbehavior in \fItele\fP mode.
X.TP
X.B \-F
XDo not fold cases. This is the default behavior in \fIg\fP mode.
X.TP
X.B \-w
XRequire match as the entire word (defined as a sequence of alphanumerics or
Xunderline).
X.TP
X.B \-g
XAssume \fIg\fP mode.
X.TP
X.B \-t
XAssume \fItele\fP mode.
X.PP
XWith multiple patterns, lines are printed if any pattern matches
X(or with \fI-v\fP, if any pattern does not match). To achieve the
Xother possible interpretation, \fIi.e.\fP print lines which contain all
X(or do not contain all) patterns, multiple instances of \fIgrep\fP or
X\fIg\fP can be cascaded in a pipeline.
X.SH FILES
X.ta 2i
X/usr/pub/phone	Public phone directory
X.br
X/usr/pub/phone.local	Public local phone directory
X.br
X/usr/pub/phone.misc	Public miscellaneous phone directory
X.br
X$HOME/phone	User's phone directory
X.br
X$HOME/Phone
X.br
X$PHONE	Settable phone directory
X.br
X.SH SEE ALSO
Xgrep(1), egrep(1), fgrep(1)
All work and no play makes Jack a dull boy
echo tele.c
sed 's/^X//' > tele.c << 'All work and no play makes Jack a dull boy'
X/*
X * tele - Search for telephone numbers and the like
X * g - Alternative GREP program with context and multiple patterns
X *	Daniel Ts'o, Rockefeller Univ.
X */
X
X#include <stdio.h>
X#include <pwd.h>
X
X#define	PHONE1LIST "/phone"
X#define	PHONE2LIST "/Phone"
X#define	PHONEPUB "/usr/pub/phone"
X#define	PHONELOCAL "/usr/pub/phone.local"
X#define	PHONEMISC "/usr/pub/phone.misc"
X#define	LINESIZE 512
X#define	MAXPATTERNS 100
X
X#define	CARAT	(0200+'^')
X#define	DOT	(0200+'.')
X#define	DOLLAR	(0200+'$')
X#define	SPECIAL	0200
X
Xint aflag = 0;
Xint bflag = 0;
Xint cflag = 0;
Xint sflag = 0;
Xint tflag = 1;
Xint vflag = 0;
Xint pflag = 0;
Xint Cflag = 0;
Xint lflag = 0;
Xint wflag = 0;
Xlong hflag = 0;
Xlong hits = 0;
Xint fileflag = 0;
Xint numberflag = 0;
Xlong lineno = 0;
Xint fold = 1;
Xchar **cv;
Xchar *ealloc();
Xchar *nodename;
Xchar *getenv();
Xlong getnum();
X
Xmain(c, v)
Xchar **v;
X{
X	register char *s;
X	register int f, f1;
X	int f2;
X	char pname[LINESIZE];
X	char obuf[BUFSIZ];
X	char *logdir();
X	char **files;
X	char *PHONE;
X	char **pp;
X	char *patterns[MAXPATTERNS+2];
X
X	/* Act like grep */
X	nodename = v[0];
X	if (*nodename == '/') {
X		for (s = nodename; *s; s++)
X			if (*s == '/')
X				nodename = s;
X		nodename++;
X	}
X	if (*nodename == 'g')
X		fold = tflag = 0;
X	PHONE = getenv("PHONE");
X	if (*PHONE == 0)
X		PHONE = NULL;
X	pp = patterns;
X	while (c > 1 && v[1][0] == '-' && v[1][1]) {
X		c--;
X		v++;
X		s = *v;
X		for (;;) {
X			if (*++s == 0)
X				break;
X			switch(*s) {
X			/* After match */
X			case 'a':
X				aflag++;
X			/* Before match */
X			case 'b':
X			case 'c':
X				if (*s == 'b')
X					bflag++;
X				cflag = getnum(s);
X				break;
X			case 'h':
X				hflag = getnum(s);
X				break;
X			case 'g':
X				fold = tflag = 0;
X				break;
X			case 'l':
X				lflag++;
X				break;
X			case 'C':
X				Cflag++;
X				break;
X			case 'p':
X				pflag++;
X				break;
X			case 's':
X				sflag++;
X				break;
X			case 't':
X				fold = tflag = 1;
X				break;
X			case 'F':
X				fold = 0;
X				break;
X			case 'i':
X			case 'f':
X				fold = 1;
X				break;
X			case 'n':
X				numberflag = 1;
X				break;
X			case 'N':
X				fileflag = 1;
X				break;
X			case 'v':
X				vflag = 1;
X				break;
X			case 'w':
X				wflag = 1;
X				break;
X			case 'e':
X				if (pp >= &patterns[MAXPATTERNS]) {
X					fprintf(stderr,
X						"%s: Too many patterns\n",
X						nodename);
X					exit(-1);
X				}
X				if (s[1]) {
X					*pp++ = s + 1;
X					*s = 0;
X					s--;
X					break;
X				}
X				else if (c > 1) {
X					c--;
X					v++;
X					*pp++ = *v;
X					break;
X				}	
X			default:
X				fprintf(stderr, "%s: %s: Bad option\n", *v,
X					nodename);
X				exit(-1);
X			}
X		}
X	}
X	if (c < 2) {
X		if (tflag)
X			fprintf(stderr, "Usage: %s names_and_patterns\n",
X				nodename);
X		else
X			fprintf(stderr, "Usage: %s pattern [files]\n",
X				nodename);
X		exit(-1);
X	}
X
X	v[c] = 0;
X	c--;
X	if (!tflag) {
X		files = v + 2;
X		*pp++ = v[1];
X		*pp = 0;
X		v = patterns;
X		if (c > 2)
X			fileflag++;
X		c = pp - patterns;
X	}
X	else
X		v++;
X	while (c-- > 0)
X		special(v[c]);
X	/* Set-up array of line buffers for context */
X	if (cflag) {
X		cv = (char **) ealloc(cflag*(sizeof (char *)));
X		for (f = 0; f < cflag; f++)
X			cv[f] = (char *)ealloc(LINESIZE);
X	}
X
X	setbuf(stdout, obuf);
X	if (tflag) {
X		strcpy(pname, logdir());
X		strcat(pname, PHONE1LIST);
X		if (PHONE != NULL) {
X			f = f1 = phone(v, PHONE);
X			f2 = phone(v, pname);
X			if (f2 >= 0) {
X				f += f2;
X				f1 = 0;
X			}
X		}
X		else
X			f = f1 = phone(v, pname);
X		strcpy(pname, logdir());
X		strcat(pname, PHONE2LIST);
X		f2 = phone(v, pname);
X		if (f2 >= 0) {
X			f += f2;
X			f1 = 0;
X		}
X		f2 = phone(v, PHONELOCAL);
X		if (f2 >= 0) {
X			f += f2;
X			f1 = 0;
X		}
X		f2 = phone(v, PHONEMISC);
X		if (f2 >= 0) {
X			f += f2;
X			f1 = 0;
X		}
X		f2 = phone(v, PHONEPUB);
X		if (f2 >= 0) {
X			f += f2;
X			f1 = 0;
X		}
X		if (f1 && !sflag)
X			fprintf(stderr, "No phone list\n");
X	}
X	else if (*files == 0)
X		f = phone(v, 0);
X	else {
X		f = 0;
X		while (*files)
X			f += phone(v, *files++);
X	}
X	if (Cflag && !sflag)
X		printf("%ld\n", hits);
X	exit(f > 0 ? 0 : -1);
X}
X
X/*
X * Do case-folding of pattern and map SPECIALS
X *	SPECIALS have 0200 bit set. Conscious decision - don't strip 0200 off
X *	pattern (maybe someone will want it)
X */
Xspecial(pat)
Xregister char *pat;
X{
X	register char *s;
X
X	s = pat;
X	while (*pat) {
X		if (fold)
X			if (*pat >= 'A' && *pat <= 'Z')
X				*pat += 'a' - 'A';
X		switch (*pat) {
X		case '\\':
X			if (pat[1])
X				pat++;
X			*s++ = *pat++;
X			break;
X		case '^':
X			*s++ = CARAT;
X			pat++;
X			break;
X		case '.':
X			*s++ = DOT;
X			pat++;
X			break;
X		case '$':
X			*s++ = DOLLAR;
X			pat++;
X			break;
X		default:
X			*s++ = *pat++;
X		}
X	}
X	*s = 0;
X}
X
Xlong getnum(s)
Xregister char *s;
X{
X	long i;
X	long atol();
X
X	s++;
X	if ('0' <= *s && *s <= '9') {
X		i = atol(s);
X		*s = 0;
X		if (i <= 0)
X			i = 1;
X	}
X	else
X		i = 1;
X	return i;
X}
X
Xphone(v, fname)
Xchar **v;
Xchar *fname;
X{
X	register char **vv, *s, *t;
X	register int i;
X	int f;
X	long ln;
X	char buf[LINESIZE], sbuf[LINESIZE];
X	FILE *pd;
X	int ccnt, cindex, nomatch;
X
X	f = 0;
X	if (fname && strcmp("-", fname)) {
X		pd = fopen(fname, "r");
X		if (pd == NULL) {
X			if (!tflag && !sflag)
X				fprintf(stderr, "%s: %s: Cannot open\n",
X					nodename, fname);
X			return -1;
X		}
X	}
X	else
X		pd = stdin;
X	cindex = 0;
X	ccnt = 0;
X	lineno = 0;
X	if (cflag)
X		for (i = 0; i < cflag; *cv[i++] = 0);
X	while (fgets(buf, LINESIZE, pd) != NULL) {
X		for (i = 0, s = buf, t = sbuf; *t++ = *s; i++, s++)
X			if (fold)
X				if (*s >= 'A' && *s <= 'Z')
X					*s = *s - 'A' + 'a';
X		lineno++;
X		nomatch = 1;
X		for (vv = v; *vv; vv++)
X			if (match(*vv, buf, i) ^ vflag) {
X				if (lflag && !sflag && !Cflag) {
X					printf("%s\n",
X						(fname ? fname : "(stdin)"));
X					fflush(stdout);
X					if (pd != stdin)
X						fclose(pd);
X					return 1;
X				}
X				f++;
X				hits++;
X				nomatch = 0;
X				if (cflag) {
X					ccnt = cindex;
X					ln = lineno - cflag;
X					if (!aflag) do {
X						output(fname, ln++, cv[cindex]);
X						*cv[cindex] = 0;
X						if (++cindex >= cflag)
X							cindex = 0;
X					} while (cindex != ccnt);
X					ccnt = cflag + 1;
X				}
X				output(fname, lineno, sbuf);
X				if (tflag)
X					fflush(stdout);
X				break;
X			}
X		if (cflag) {
X			if (nomatch && ccnt && !bflag) {
X				output(fname, lineno, sbuf);
X				if (tflag)
X					fflush(stdout);
X				*cv[cindex] = 0;
X			}
X			else if (!nomatch)
X				*cv[cindex] = 0;
X			else {
X				t = cv[cindex];
X				s = sbuf;
X				while (*t++ = *s++);
X			}
X			if (++cindex >= cflag)
X				cindex = 0;
X		}
X		if (ccnt) {
X			if (pflag && ccnt == 1 && *sbuf != '\n')
X				ccnt = 2;
X			ccnt--;
X		}
X		if (hflag && ccnt == 0 && hits >= hflag) {
X			fflush(stdout);
X			exit(0);
X		}
X	}
X	if (pd != stdin)
X		fclose(pd);
X	fflush(stdout);
X	return f;
X}
X
Xoutput(fname, ln, s)
Xchar *fname;
Xlong ln;
Xchar *s;
X{
X	if (s == 0 || *s == 0)
X		return;
X	if (Cflag)
X		return;
X	if (sflag)
X		return;
X	if (fileflag && fname)
X		printf("%s:", fname);
X	if (numberflag)
X		printf("%ld:", ln);
X	if (fputs(s, stdout) == EOF) {
X		fprintf(stderr, "%s: Output error\n", nodename);
X		exit(-1);
X	}
X}
X
Xmatch(a, b, m)
Xchar *a, *b;
Xregister int m;
X{
X	register char *s, *t;
X	char *bb;
X	int n;
X	int carat;
X
X	if ((*a&0377) == CARAT) {
X		a++;
X		if (*a == 0)
X			return 1;
X		carat = 1;
X	}
X	else
X		carat = 0;
X	n = 0;
X	s = a;
X	if (*s == 0)
X		return 0;
X	while (*s++)
X		n++;
X	bb = b;
X	for (;;) {
X		s = a;
X		t = b;
X		/* Try to find a place to begin match */
X		if ((*s&0377) != DOT)
X			for (;;) {
X				/* At end of line ? */
X				if (*t == 0)
X					break;
X				/* Found beginning of possible match */
X				if (*t++ == *s) {
X					t--;
X					break;
X				}
X				m--;
X			}
X		/* Must match occur at beginning of line ? */
X		if (carat && t != b)
X			break;
X		/* Not enough characters left for a match */
X		if (n > m)
X			break;
X		/* Is it at a start of a word ? */
X		b = t;
X		if (wflag) {
X			b--;
X			if (t != bb && (*b == '_'
X				|| ('a' <= *b && *b <= 'z')
X				|| ('A' <= *b && *b <= 'Z')
X				|| ('0' <= *b && *b <= '9'))) {
X				b++;
X				m--;
X				continue;
X			}
X		}
X		for (;;) {
X			/* Match end of line */
X			if (*t == '\n') {
X				if ((*s&0377) == DOLLAR)
X					return 1;
X				break;
X			}
X			/* Match anything */
X			if ((*s&0377) == DOT) {
X				s++;
X				t++;
X			}
X			else if (*s++ != *t++)
X				break;
X			/* Complete match */
X			if (*s == 0) {
X				if (wflag && *t != '\n' && (*t == '_'
X					|| ('a' <= *t && *t <= 'z')
X					|| ('A' <= *t && *t <= 'Z')
X					|| ('0' <= *t && *t <= '9')))
X					break;
X				return 1;
X			}
X		}
X		/* Could not match beginning of line */
X		if (carat)
X			break;
X		m--;
X		b++;
X	}
X	return 0;
X}
X
Xchar *logdir()
X{
X	char *getenv();
X	struct passwd *getpwuid();
X	register char *hd;
X	register struct passwd *p;
X
X	hd = getenv("HOME");
X	if (hd == NULL || *hd == 0) {
X		p = getpwuid(getuid());
X		hd = p->pw_dir;
X	}
X	if (hd == NULL || *hd == 0)
X		hd = "/";
X	return hd;
X}
X
Xchar *ealloc(n)
Xint n;
X{
X	register char *s;
X
X	s = (char *)malloc(n);
X	if (s == NULL) {
X		fprintf(stderr, "malloc: %s: Out of memory\n", nodename);
X		exit(-1);
X	}
X	return s;
X}
All work and no play makes Jack a dull boy