xref: /titanic_41/usr/src/lib/libcmd/common/grep.c (revision bbb1277b6ec1b0daad4e3ed1a2b891d3e2ece2eb)
1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *           Copyright (c) 1995-2009 AT&T Knowledge Ventures            *
5 *                      and is licensed under the                       *
6 *                  Common Public License, Version 1.0                  *
7 *                      by AT&T Knowledge Ventures                      *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *            http://www.opensource.org/licenses/cpl1.0.txt             *
11 *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                 Glenn Fowler <gsf@research.att.com>                  *
18 *                                                                      *
19 ***********************************************************************/
20 #pragma prototyped
21 
22 static const char usage[] =
23 "[-?\n@(#)$Id: grep (AT&T Research) 2006-06-14 $\n]"
24 USAGE_LICENSE
25 "[+NAME?grep - search lines in files for matching patterns]"
26 "[+DESCRIPTION?The \bgrep\b commands search the named input files"
27 "	for lines containing a match for the given \apatterns\a."
28 "	Matching lines are printed by default. The standard input is searched"
29 "	if no files are given or when the file \b-\b is specified.]"
30 "[+?There are six variants of \bgrep\b, each one using a different form of"
31 "	\apattern\a, controlled either by option or the command path"
32 "	base name. Details of each variant may be found in \bregex\b(3).]"
33 "	{"
34 "	[+grep?The default basic regular expressions (no alternations.)]"
35 "	[+egrep?Extended regular expressions (alternations, one or more.)]"
36 "	[+pgrep?\bperl\b(1) regular expressions (lenient extended.)]"
37 "	[+xgrep?Augmented regular expressions (conjunction, negation.)]"
38 "	[+fgrep?Fixed string expressions.]"
39 "	[+agrep?Approximate regular expressions (not implemented.)]"
40 "	}"
41 "[G:basic-regexp?\bgrep\b mode (default): basic regular expression \apatterns\a.]"
42 "[E:extended-regexp?\begrep\b mode: extended regular expression \apatterns\a.]"
43 "[X:augmented-regexp?\bxgrep\b mode: augmented regular expression \apatterns\a.]"
44 "[P:perl-regexp?\bpgrep\b mode: \bperl\b(1) regular expression \apatterns\a.]"
45 "[F:fixed-string?\bfgrep\b mode: fixed string \apatterns\a.]"
46 "[A:approximate-regexp?\bagrep\b mode: approximate regular expression \apatterns\a (not implemented.)]"
47 
48 "[C:context?Set the matched line context \abefore\a and \aafter\a count."
49 "	By default only matched lines are printed.]:?"
50 "		[before[,after]]:=2,2]"
51 "[c:count?Only print a matching line count for each file.]"
52 "[e:expression|pattern|regexp?Specify a matching \apattern\a. More than one"
53 "	\apattern\a implies alternation. If this option is specified"
54 "	then the command line \apattern\a must be omitted.]:"
55 "		[pattern]"
56 "[f:file?Each line in \apattern-file\a is a \apattern\a, placed into a single"
57 "	alternating expression.]:"
58 "		[pattern-file]"
59 "[H:filename|with-filename?Prefix each matched line with the containing file name.]"
60 "[h:no-filename?Suppress containing file name prefix for each matched line.]"
61 "[i:ignore-case?Ignore case when matching.]"
62 "[l:files-with-matches?Only print file names with at least one match.]"
63 "[L:files-without-matches?Only print file names with no matches.]"
64 "[b:highlight?Highlight matches using the ansi terminal bold sequence.]"
65 "[v:invert-match|revert-match?Invert the \apattern\a match sense.]"
66 "[m:label?All patterns must be of the form \alabel\a:\apattern\a. Match and"
67 "	count output will be prefixed by the corresponding \alabel\a:.]"
68 "[O:lenient?Enable lenient \apattern\a interpretation. This is the default.]"
69 "[x:line-match|line-regexp?Force \apatterns\a to match complete lines.]"
70 "[n:number|line-number?Prefix each matched line with its line number.]"
71 "[N:name?Set the standard input file name prefix to"
72 "	\aname\a.]:[name:=empty]"
73 "[q:quiet|silent?Do not print matching lines.]"
74 "[S:strict?Enable strict \apattern\a interpretation with diagnostics.]"
75 "[s:suppress|no-messages?Suppress error and warning messages.]"
76 "[t:total?Only print a single matching line count for all files.]"
77 "[T:test?Enable implementation specific tests.]:"
78 "		[test]"
79 "[w:word-match|word-regexp?Force \apatterns\a to match complete words.]"
80 "[a?Ignored for GNU compatibility.]"
81 "\n"
82 "\n[ pattern ] [ file ... ]\n"
83 "\n"
84 "[+DIAGNOSTICS?Exit status 0 if matches were found, 1 if no matches were found,"
85 "	where \b-v\b invertes the exit status. Exit status 2 for other"
86 "	errors that are accompanied by a message on the standard error.]"
87 "[+SEE ALSO?\bed\b(1), \bsed\b(1), \bperl\b(1), \bregex\b(3)]"
88 "[+CAVEATS?Some expressions of necessity require exponential space"
89 "	and/or time.]"
90 "[+BUGS?Some expressions may use sub-optimal algorithms. For example,"
91 "	don't use this implementation to compute primes.]"
92 ;
93 
94 #include <ast.h>
95 #include <ctype.h>
96 #include <ccode.h>
97 #include <error.h>
98 #include <regex.h>
99 
100 #ifndef EISDIR
101 #define EISDIR		(-1)
102 #endif
103 
104 /*
105  * snarfed from Doug McElroy's C++ version
106  *
107  * this grep is based on the Posix re package.
108  * unfortunately it has to have a nonstandard interface.
109  * 1. fgrep does not have usual operators. REG_LITERAL
110  * caters for this.
111  * 2. grep allows null expressions, hence REG_NULL.
112  * 3. it may be possible to combine the multiple
113  * patterns of grep into single patterns.  important
114  * special cases are handled by regcomb().
115  * 4. anchoring by -x has to be done separately from
116  * compilation (remember that fgrep has no ^ or $ operator),
117  * hence REG_LEFT|REG_RIGHT.  (An honest, but slow alternative:
118  * run regexec with REG_NOSUB off and nmatch=1 and check
119  * whether the match is full length)
120  */
121 
122 typedef struct Item_s			/* list item - sue me for waste	*/
123 {
124 	struct Item_s*	next;		/* next in list			*/
125 	regex_t		re;		/* compiled re			*/
126 	Sfulong_t	hits;		/* labeled pattern matches	*/
127 	Sfulong_t	total;		/* total hits			*/
128 	char		string[1];	/* string value			*/
129 } Item_t;
130 
131 typedef struct List_s			/* generic list			*/
132 {
133 	Item_t*		head;		/* list head			*/
134 	Item_t*		tail;		/* list tail			*/
135 } List_t;
136 
137 typedef struct State_s			/* program state		*/
138 {
139 	struct
140 	{
141 	char*		base;		/* sfsetbuf buffer		*/
142 	size_t		size;		/* sfsetbuf size		*/
143 	int		noshare;	/* turn off SF_SHARE		*/
144 	}		buffer;
145 
146 	List_t		file;		/* pattern file list		*/
147 	List_t		pattern;	/* pattern list			*/
148 	List_t		re;		/* re list			*/
149 
150 	regmatch_t	posvec[1];	/* match position vector	*/
151 	regmatch_t*	pos;		/* match position pointer	*/
152 	int		posnum;		/* number of match positions	*/
153 
154 	int		any;		/* if any pattern hit		*/
155 	int		list;		/* list files with hits		*/
156 	int		notfound;	/* some input file not found	*/
157 	int		options;	/* regex options		*/
158 
159 	Sfulong_t	hits;		/* total matched pattern count	*/
160 
161 	unsigned char	byline;		/* multiple pattern line by line*/
162 	unsigned char	count;		/* count number of hits		*/
163 	unsigned char	label;		/* all patterns labeled		*/
164 	unsigned char	match;		/* match sense			*/
165 	unsigned char	query;		/* return status but no output	*/
166 	unsigned char	number;		/* line numbers			*/
167 	unsigned char	prefix;		/* print file prefix		*/
168 	unsigned char	suppress;	/* no unopenable file messages	*/
169 	unsigned char	words;		/* word matches only		*/
170 } State_s;
171 
172 static void
173 addre(State_s *state, List_t* p, char* s)
174 {
175 	int	c;
176 	char*	b;
177 	Item_t*	x;
178 	Sfio_t*	t;
179 
180 	b = s;
181 	if (state->label)
182 	{
183 		if (!(s = strchr(s, ':')))
184 			error(3, "%s: label:pattern expected", b);
185 		c = s - b;
186 		s++;
187 	}
188 	else
189 		c = 0;
190 	if (!(x = newof(0, Item_t, 1, c)))
191 		error(ERROR_SYSTEM|3, "out of space (pattern `%s')", b);
192 	if (c)
193 		memcpy(x->string, b, c);
194 	if (state->words)
195 	{
196 		if (!(t = sfstropen()))
197 			error(ERROR_SYSTEM|3, "out of space (word pattern `%s')", s);
198 		if (!(state->options & REG_AUGMENTED))
199 			sfputc(t, '\\');
200 		sfputc(t, '<');
201 		sfputr(t, s, -1);
202 		if (!(state->options & REG_AUGMENTED))
203 			sfputc(t, '\\');
204 		sfputc(t, '>');
205 		if (!(s = sfstruse(t)))
206 			error(ERROR_SYSTEM|3, "out of space");
207 	}
208 	else
209 		t = 0;
210 	if (c = regcomp(&x->re, s, state->options|REG_MULTIPLE))
211 		regfatal(&x->re, 3, c);
212 	if (t)
213 		sfstrclose(t);
214 	if (!p->head)
215 	{
216 		p->head = p->tail = x;
217 		if (state->number || !regrecord(&x->re))
218 			state->byline = 1;
219 	}
220 	else if (state->label || regcomb(&p->tail->re, &x->re))
221 	{
222 		p->tail = p->tail->next = x;
223 		if (!state->byline && (state->number || !state->label || !regrecord(&x->re)))
224 			state->byline = 1;
225 	}
226 	else
227 		free(x);
228 }
229 
230 static void
231 addstring(State_s *state, List_t* p, char* s)
232 {
233 	Item_t*	x;
234 
235 	if (!(x = newof(0, Item_t, 1, strlen(s))))
236 		error(ERROR_SYSTEM|3, "out of space (string `%s')", s);
237 	strcpy(x->string, s);
238 	if (p->head)
239 		p->tail->next = x;
240 	else
241 		p->head = x;
242 	p->tail = x;
243 }
244 
245 static void
246 compile(State_s *state)
247 {
248 	int	line;
249 	size_t	n;
250 	char*	s;
251 	char*	t;
252 	char*	file;
253 	Item_t*	x;
254 	Sfio_t*	f;
255 
256 	for (x = state->pattern.head; x; x = x->next)
257 		addre(state, &state->re, x->string);
258 	for (x = state->file.head; x; x = x->next)
259 	{
260 		s = x->string;
261 		if (!(f = sfopen(NiL, s, "r")))
262 			error(ERROR_SYSTEM|4, "%s: cannot open", s);
263 		else
264 		{
265 			file = error_info.file;
266 			error_info.file = s;
267 			line = error_info.line;
268 			error_info.line = 0;
269 			while (s = (char*)sfreserve(f, SF_UNBOUND, SF_LOCKR))
270 			{
271 				if (!(n = sfvalue(f)))
272 					break;
273 				if (s[n - 1] != '\n')
274 				{
275 					for (t = s + n; t > s && *--t != '\n'; t--);
276 					if (t == s)
277 					{
278 						sfread(f, s, 0);
279 						break;
280 					}
281 					n = t - s + 1;
282 				}
283 				s[n - 1] = 0;
284 				addre(state, &state->re, s);
285 				s[n - 1] = '\n';
286 				sfread(f, s, n);
287 			}
288 			while ((s = sfgetr(f, '\n', 1)) || (s = sfgetr(f, '\n', -1)))
289 			{
290 				error_info.line++;
291 				addre(state, &state->re, s);
292 			}
293 			error_info.file = file;
294 			error_info.line = line;
295 			sfclose(f);
296 		}
297 	}
298 	if (!state->re.head)
299 		error(3, "no pattern");
300 }
301 
302 static void
303 highlight(Sfio_t* sp, const char* s, int n, int so, int eo)
304 {
305 	static const char	bold[] =	{CC_esc,'[','1','m'};
306 	static const char	normal[] =	{CC_esc,'[','0','m'};
307 
308 	sfwrite(sp, s, so);
309 	sfwrite(sp, bold, sizeof(bold));
310 	sfwrite(sp, s + so, eo - so);
311 	sfwrite(sp, normal, sizeof(normal));
312 	sfwrite(sp, s + eo, n - eo);
313 }
314 
315 typedef struct
316 {
317     State_s *state;
318     Item_t  *item;
319 } record_handle;
320 
321 static int
322 record(void* handle, const char* s, size_t len)
323 {
324 	record_handle	*r_x = (record_handle *)handle;
325 	State_s		*state = r_x->state;
326 	Item_t		*item  = r_x->item;
327 
328 	item->hits++;
329 	if (state->query || state->list)
330 		return -1;
331 	if (!state->count)
332 	{
333 		if (state->prefix)
334 			sfprintf(sfstdout, "%s:", error_info.file);
335 		if (state->label)
336 			sfprintf(sfstdout, "%s:", item->string);
337 		if (state->pos)
338 			highlight(sfstdout, s, len + 1, state->pos[0].rm_so, state->pos[0].rm_eo);
339 		else
340 			sfwrite(sfstdout, s, len + 1);
341 	}
342 	return 0;
343 }
344 
345 static void
346 execute(State_s *state, Sfio_t* input, char* name)
347 {
348 	register char*	s;
349 	char*		file;
350 	Item_t*		x;
351 	size_t		len;
352 	int		result;
353 	int		line;
354 
355 	Sfulong_t	hits = 0;
356 
357 	if (state->buffer.noshare)
358 		sfset(input, SF_SHARE, 0);
359 	if (state->buffer.size)
360 		sfsetbuf(input, state->buffer.base, state->buffer.size);
361 	if (!name)
362 		name = "/dev/stdin";
363 	file = error_info.file;
364 	error_info.file = name;
365 	line = error_info.line;
366 	error_info.line = 0;
367 	if (state->byline)
368 	{
369 		for (;;)
370 		{
371 			error_info.line++;
372 			if (s = sfgetr(input, '\n', 0))
373 				len = sfvalue(input) - 1;
374 			else if (s = sfgetr(input, '\n', -1))
375 			{
376 				len = sfvalue(input);
377 				s[len] = '\n';
378 #if _you_like_the_noise
379 				error(1, "newline appended");
380 #endif
381 			}
382 			else
383 			{
384 				if (sferror(input) && errno != EISDIR)
385 					error(ERROR_SYSTEM|2, "read error");
386 				break;
387 			}
388 			x = state->re.head;
389 			do
390 			{
391 				if (!(result = regnexec(&x->re, s, len, state->posnum, state->pos, 0)))
392 				{
393 					if (!state->label)
394 						break;
395 					x->hits++;
396 					if (state->query || state->list)
397 						goto done;
398 					if (!state->count)
399 					{
400 						if (state->prefix)
401 							sfprintf(sfstdout, "%s:", name);
402 						if (state->number)
403 							sfprintf(sfstdout, "%d:", error_info.line);
404 						sfprintf(sfstdout, "%s:", x->string);
405 						if (state->pos)
406 							highlight(sfstdout, s, len + 1, state->pos[0].rm_so, state->pos[0].rm_eo);
407 						else
408 							sfwrite(sfstdout, s, len + 1);
409 					}
410 				}
411 				else if (result != REG_NOMATCH)
412 					regfatal(&x->re, 3, result);
413 			} while (x = x->next);
414 			if (!state->label && (x != 0) == state->match)
415 			{
416 				hits++;
417 				if (state->query || state->list)
418 					break;
419 				if (!state->count)
420 				{
421 					if (state->prefix)
422 						sfprintf(sfstdout, "%s:", name);
423 					if (state->number)
424 						sfprintf(sfstdout, "%d:", error_info.line);
425 					if (state->pos)
426 						highlight(sfstdout, s, len + 1, state->pos[0].rm_so, state->pos[0].rm_eo);
427 					else
428 						sfwrite(sfstdout, s, len + 1);
429 				}
430 			}
431 		}
432 	}
433 	else
434 	{
435 		register char*	e;
436 		register char*	t;
437 		char*		r;
438 
439 		static char*	span = 0;
440 		static size_t	spansize = 0;
441 
442 		s = e = 0;
443 		for (;;)
444 		{
445 			if (s < e)
446 			{
447 				t = span;
448 				for (;;)
449 				{
450 					len = 2 * (e - s) + t - span + 1;
451 					len = roundof(len, SF_BUFSIZE);
452 					if (spansize < len)
453 					{
454 						spansize = len;
455 						len = t - span;
456 						if (!(span = newof(span, char, spansize, 0)))
457 							error(ERROR_SYSTEM|3, "%s: line longer than %lu characters", name, len + e - s);
458 						t = span + len;
459 					}
460 					len = e - s;
461 					memcpy(t, s, len);
462 					t += len;
463 					if (!(s = sfreserve(input, SF_UNBOUND, 0)) || (len = sfvalue(input)) <= 0)
464 					{
465 						if ((sfvalue(input) || sferror(input)) && errno != EISDIR)
466 							error(ERROR_SYSTEM|2, "%s: read error", name);
467 						break;
468 					}
469 					else if (!(e = memchr(s, '\n', len)))
470 						e = s + len;
471 					else
472 					{
473 						r = s + len;
474 						len = (e - s) + t - span;
475 						len = roundof(len, SF_BUFSIZE);
476 						if (spansize < len)
477 						{
478 							spansize = len;
479 							len = t - span;
480 							if (!(span = newof(span, char, spansize, 0)))
481 								error(ERROR_SYSTEM|3, "%s: line longer than %lu characters", name, len + e - s);
482 							t = span + len;
483 						}
484 						len = e - s;
485 						memcpy(t, s, len);
486 						t += len;
487 						s += len + 1;
488 						e = r;
489 						break;
490 					}
491 				}
492 				*t = '\n';
493 				x = state->re.head;
494 				do
495 				{
496 					record_handle r_x = { state, x };
497 					if ((result = regrexec(&x->re, span, t - span, state->posnum, state->pos, state->options, '\n', (void*)&r_x, record)) < 0)
498 						goto done;
499 					if (result && result != REG_NOMATCH)
500 						regfatal(&x->re, 3, result);
501 				} while (x = x->next);
502 				if (!s)
503 					break;
504 			}
505 			else
506 			{
507 				if (!(s = sfreserve(input, SF_UNBOUND, 0)))
508 				{
509 					if ((sfvalue(input) || sferror(input)) && errno != EISDIR)
510 						error(ERROR_SYSTEM|2, "%s: read error", name);
511 					break;
512 				}
513 				if ((len = sfvalue(input)) <= 0)
514 					break;
515 				e = s + len;
516 			}
517 			t = e;
518 			while (t > s)
519 				if (*--t == '\n')
520 				{
521 					x = state->re.head;
522 					do
523 					{
524 						record_handle r_x = { state, x };
525 						if ((result = regrexec(&x->re, s, t - s, state->posnum, state->pos, state->options, '\n', (void*)&r_x, record)) < 0)
526 							goto done;
527 						if (result && result != REG_NOMATCH)
528 							regfatal(&x->re, 3, result);
529 					} while (x = x->next);
530 					s = t + 1;
531 					break;
532 				}
533 		}
534 	}
535  done:
536 	error_info.file = file;
537 	error_info.line = line;
538 	if (state->byline && !state->label)
539 	{
540 		if (hits && state->list >= 0)
541 			state->any = 1;
542 		if (!state->query)
543 		{
544 			if (!state->list)
545 			{
546 				if (state->count)
547 				{
548 					if (state->count & 2)
549 						state->hits += hits;
550 					else
551 					{
552 						if (state->prefix)
553 							sfprintf(sfstdout, "%s:", name);
554 						sfprintf(sfstdout, "%I*u\n", sizeof(hits), hits);
555 					}
556 				}
557 			}
558 			else if ((hits != 0) == (state->list > 0))
559 			{
560 				if (state->list < 0)
561 					state->any = 1;
562 				sfprintf(sfstdout, "%s\n", name);
563 			}
564 		}
565 	}
566 	else
567 	{
568 		x = state->re.head;
569 		do
570 		{
571 			if (x->hits && state->list >= 0)
572 			{
573 				state->any = 1;
574 				if (state->query)
575 					break;
576 			}
577 			if (!state->query)
578 			{
579 				if (!state->list)
580 				{
581 					if (state->count)
582 					{
583 						if (state->count & 2)
584 						{
585 							x->total += x->hits;
586 							state->hits += x->hits;
587 						}
588 						else
589 						{
590 							if (state->prefix)
591 								sfprintf(sfstdout, "%s:", name);
592 							if (state->label)
593 								sfprintf(sfstdout, "%s:", x->string);
594 							sfprintf(sfstdout, "%I*u\n", sizeof(x->hits), x->hits);
595 						}
596 					}
597 				}
598 				else if ((x->hits != 0) == (state->list > 0))
599 				{
600 					if (state->list < 0)
601 						state->any = 1;
602 					if (state->label)
603 						sfprintf(sfstdout, "%s:%s\n", name, x->string);
604 					else
605 						sfprintf(sfstdout, "%s\n", name);
606 				}
607 			}
608 			x->hits = 0;
609 		} while (x = x->next);
610 	}
611 }
612 
613 
614 static
615 int grep_main(int argc, char** argv, void *context)
616 {
617 	int	c;
618 	char*	s;
619 	char*	h;
620 	Sfio_t*	f;
621 	State_s state;
622 	memset(&state, 0, sizeof(state));
623 
624 	NoP(argc);
625 	state.match = 1;
626 	state.options = REG_FIRST|REG_NOSUB|REG_NULL;
627 	h = 0;
628 	if (strcmp(astconf("CONFORMANCE", NiL, NiL), "standard"))
629 		state.options |= REG_LENIENT;
630 	if (s = strrchr(argv[0], '/'))
631 		s++;
632 	else
633 		s = argv[0];
634 	switch (*s)
635 	{
636 	case 'e':
637 	case 'E':
638 		s = "egrep";
639 		state.options |= REG_EXTENDED;
640 		break;
641 	case 'f':
642 	case 'F':
643 		s = "fgrep";
644 		state.options |= REG_LITERAL;
645 		break;
646 	case 'p':
647 	case 'P':
648 		s = "pgrep";
649 		state.options |= REG_EXTENDED|REG_LENIENT;
650 		break;
651 	case 'x':
652 	case 'X':
653 		s = "xgrep";
654 		state.options |= REG_AUGMENTED;
655 		break;
656 	default:
657 		s = "grep";
658 		break;
659 	}
660 	error_info.id = s;
661 	while (c = optget(argv, usage))
662 		switch (c)
663 		{
664 		case 'E':
665 			state.options |= REG_EXTENDED;
666 			break;
667 		case 'F':
668 			state.options |= REG_LITERAL;
669 			break;
670 		case 'G':
671 			state.options &= ~(REG_AUGMENTED|REG_EXTENDED);
672 			break;
673 		case 'H':
674 			state.prefix = opt_info.num;
675 			break;
676 		case 'L':
677 			state.list = -opt_info.num;
678 			break;
679 		case 'N':
680 			h = opt_info.arg;
681 			break;
682 		case 'O':
683 			state.options |= REG_LENIENT;
684 			break;
685 		case 'P':
686 			state.options |= REG_EXTENDED|REG_LENIENT;
687 			break;
688 		case 'S':
689 			state.options &= ~REG_LENIENT;
690 			break;
691 		case 'T':
692 			s = opt_info.arg;
693 			switch (*s)
694 			{
695 			case 'b':
696 			case 'm':
697 				c = *s++;
698 				state.buffer.size = strton(s, &s, NiL, 1);
699 				if (c == 'b' && !(state.buffer.base = newof(0, char, state.buffer.size, 0)))
700 					error(ERROR_SYSTEM|3, "out of space [test buffer]");
701 				if (*s)
702 					error(3, "%s: invalid characters after test", s);
703 				break;
704 			case 'f':
705 				state.options |= REG_FIRST;
706 				break;
707 			case 'l':
708 				state.options |= REG_LEFT;
709 				break;
710 			case 'n':
711 				state.buffer.noshare = 1;
712 				break;
713 			case 'r':
714 				state.options |= REG_RIGHT;
715 				break;
716 			default:
717 				error(3, "%s: unknown test", s);
718 				break;
719 			}
720 			break;
721 		case 'X':
722 			state.options |= REG_AUGMENTED;
723 			break;
724 		case 'a':
725 			break;
726 		case 'b':
727 			state.options &= ~(REG_FIRST|REG_NOSUB);
728 			break;
729 		case 'c':
730 			state.count |= 1;
731 			break;
732 		case 'e':
733 			addstring(&state, &state.pattern, opt_info.arg);
734 			break;
735 		case 'f':
736 			addstring(&state, &state.file, opt_info.arg);
737 			break;
738 		case 'h':
739 			state.prefix = 2;
740 			break;
741 		case 'i':
742 			state.options |= REG_ICASE;
743 			break;
744 		case 'l':
745 			state.list = opt_info.num;
746 			break;
747 		case 'm':
748 			state.label = 1;
749 			break;
750 		case 'n':
751 			state.number = 1;
752 			break;
753 		case 'q':
754 			state.query = 1;
755 			break;
756 		case 's':
757 			state.suppress = opt_info.num;
758 			break;
759 		case 't':
760 			state.count |= 2;
761 			break;
762 		case 'v':
763 			if (state.match = !opt_info.num)
764 				state.options &= ~REG_INVERT;
765 			else
766 				state.options |= REG_INVERT;
767 			break;
768 		case 'w':
769 			state.words = 1;
770 			break;
771 		case 'x':
772 			state.options |= REG_LEFT|REG_RIGHT;
773 			break;
774 		case '?':
775 			error(ERROR_USAGE|4, "%s", opt_info.arg);
776 			break;
777 		case ':':
778 			error(2, "%s", opt_info.arg);
779 			break;
780 		default:
781 			error(3, "%s: not implemented", opt_info.name);
782 			break;
783 		}
784 	argv += opt_info.index;
785 	if ((state.options & REG_LITERAL) && (state.options & (REG_AUGMENTED|REG_EXTENDED)))
786 		error(3, "-F and -A or -P or -X are incompatible");
787 	if ((state.options & REG_LITERAL) && state.words)
788 		error(ERROR_SYSTEM|3, "-F and -w are incompatible");
789 	if (!state.file.head && !state.pattern.head)
790 	{
791 		if (!argv[0])
792 			error(3, "no pattern");
793 		addstring(&state, &state.pattern, *argv++);
794 	}
795 	if (!(state.options & (REG_FIRST|REG_NOSUB)))
796 	{
797 		if (state.count || state.list || state.query || (state.options & REG_INVERT))
798 			state.options |= REG_FIRST|REG_NOSUB;
799 		else
800 		{
801 			state.pos = state.posvec;
802 			state.posnum = elementsof(state.posvec);
803 		}
804 	}
805 	compile(&state);
806 	if (!argv[0])
807 	{
808 		state.prefix = h ? 1 : 0;
809 		execute(&state, sfstdin, h);
810 	}
811 	else
812 	{
813 		if (state.prefix > 1)
814 			state.prefix = 0;
815 		else if (argv[1])
816 			state.prefix = 1;
817 		while (s = *argv++)
818 		{
819 			if (f = sfopen(NiL, s, "r"))
820 			{
821 				execute(&state, f, s);
822 				sfclose(f);
823 				if (state.query && state.any)
824 					break;
825 			}
826 			else
827 			{
828 				state.notfound = 1;
829 				if (!state.suppress)
830 					error(ERROR_SYSTEM|2, "%s: cannot open", s);
831 			}
832 		}
833 	}
834 	if ((state.count & 2) && !state.query && !state.list)
835 	{
836 		if (state.label)
837 		{
838 			Item_t*		x;
839 
840 			x = state.re.head;
841 			do
842 			{
843 				sfprintf(sfstdout, "%s:%I*u\n", x->string, sizeof(x->total), x->total);
844 			} while (x = x->next);
845 		}
846 		else
847 			sfprintf(sfstdout, "%I*u\n", sizeof(state.hits), state.hits);
848 	}
849 	return (state.notfound && !state.query) ? 2 : !state.any;
850 }
851 
852 
853 int b_egrep(int argc, char** argv, void *context)
854 {
855 	return grep_main(argc, argv, context);
856 }
857 
858 int b_grep(int argc, char** argv, void *context)
859 {
860 	return grep_main(argc, argv, context);
861 }
862 
863 int b_fgrep(int argc, char** argv, void *context)
864 {
865 	return grep_main(argc, argv, context);
866 }
867 
868 int b_pgrep(int argc, char** argv, void *context)
869 {
870 	return grep_main(argc, argv, context);
871 }
872 
873 int b_xgrep(int argc, char** argv, void *context)
874 {
875 	return grep_main(argc, argv, context);
876 }
877