1
2#include "config.h"
3#include "queue.h"
4#include "util.h"
5
6#include <regex.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <strings.h>
11
12enum { Match = 0, NoMatch = 1, Error = 2 };
13
14static void addpattern(const char *);
15static void addpatternfile(FILE *);
16static int grep(FILE *, const char *);
17
18static int Eflag;
19static int Fflag;
20static int Hflag;
21static int eflag;
22static int fflag;
23static int hflag;
24static int iflag;
25static int sflag;
26static int vflag;
27static int wflag;
28static int xflag;
29static int many;
30static int mode;
31#if FEATURE_GREP_CONTEXT
32static long Aflag = 0;
33static long Bflag = 0;
34#endif
35#if FEATURE_GREP_MAX_COUNT
36static long mval = -1;
37#endif
38
39struct pattern {
40 regex_t preg;
41 SLIST_ENTRY(pattern) entry;
42 char pattern[];
43};
44
45static SLIST_HEAD(phead, pattern) phead;
46
47static void
48addpattern(const char *pattern)
49{
50 struct pattern *pnode;
51 size_t patlen;
52
53 patlen = strlen(pattern);
54
55 pnode = enmalloc(Error, sizeof(*pnode) + patlen + 9);
56 SLIST_INSERT_HEAD(&phead, pnode, entry);
57
58 if (Fflag || (!xflag && !wflag)) {
59 memcpy(pnode->pattern, pattern, patlen + 1);
60 } else {
61 sprintf(
62 pnode->pattern,
63 "%s%s%s%s%s",
64 xflag ? "^" : "\\<",
65 Eflag ? "(" : "\\(",
66 pattern,
67 Eflag ? ")" : "\\)",
68 xflag ? "$" : "\\>"
69 );
70 }
71}
72
73static void
74addpatternfile(FILE *fp)
75{
76 static char *buf = NULL;
77 static size_t size = 0;
78 ssize_t len = 0;
79
80 while ((len = getline(&buf, &size, fp)) > 0) {
81 if (buf[len - 1] == '\n')
82 buf[len - 1] = '\0';
83 addpattern(buf);
84 }
85 if (ferror(fp))
86 enprintf(Error, "read error:");
87}
88
89#if FEATURE_GREP_CONTEXT
90static void
91print_line(const char *str, const char *line, long line_no, char sep)
92{
93 if (!hflag && (many || Hflag))
94 printf("%s%c", str, sep);
95 if (mode == 'n')
96 printf("%ld%c", line_no, sep);
97 puts(line);
98}
99#endif
100
101static int
102grep(FILE *fp, const char *str)
103{
104 static char *buf = NULL;
105 static size_t size = 0;
106 ssize_t len = 0;
107 long c = 0, n;
108 struct pattern *pnode;
109 int match, result = NoMatch;
110#if FEATURE_GREP_MAX_COUNT
111 long matches = 0;
112#endif
113#if FEATURE_GREP_CONTEXT
114 struct context_line {
115 char *str;
116 long line_no;
117 } *before_buf = NULL;
118 size_t before_head = 0, before_count = 0, i = 0, idx = 0;
119 long after_left = 0;
120 long last_printed_line = 0;
121
122 if (Bflag > 0 && !(mode == 'c' || mode == 'l' || mode == 'q'))
123 before_buf = ecalloc(Bflag, sizeof(*before_buf));
124#endif
125
126 for (n = 1; (len = getline(&buf, &size, fp)) > 0; n++) {
127 /* remove the trailing newline if one is present */
128 if (buf[len - 1] == '\n')
129 buf[len - 1] = '\0';
130 match = 0;
131 SLIST_FOREACH(pnode, &phead, entry)
132 {
133 if (Fflag) {
134 if (xflag) {
135 if (!(iflag ? strcasecmp : strcmp)(buf, pnode->pattern)) {
136 match = 1;
137 break;
138 }
139 } else {
140 if ((iflag ? strcasestr : strstr)(buf, pnode->pattern)) {
141 match = 1;
142 break;
143 }
144 }
145 } else {
146 if (regexec(&pnode->preg, buf, 0, NULL, 0) == 0) {
147 match = 1;
148 break;
149 }
150 }
151 }
152 if (match != vflag) {
153 result = Match;
154#if FEATURE_GREP_MAX_COUNT
155 matches++;
156#endif
157 switch (mode) {
158 case 'c':
159 c++;
160 break;
161 case 'l':
162 puts(str);
163 goto end;
164 case 'q':
165 exit(Match);
166 default:
167#if FEATURE_GREP_CONTEXT
168 if (Aflag > 0 || Bflag > 0) {
169 if (last_printed_line > 0 && n > last_printed_line + 1)
170 puts("--");
171 for (i = 0; i < before_count; i++) {
172 idx = (before_head - before_count + i + Bflag) % Bflag;
173 print_line(str, before_buf[idx].str, before_buf[idx].line_no, '-');
174 free(before_buf[idx].str);
175 before_buf[idx].str = NULL;
176 }
177 before_count = 0;
178 before_head = 0;
179 print_line(str, buf, n, ':');
180 after_left = Aflag;
181 last_printed_line = n;
182 } else {
183#endif
184 if (!hflag && (many || Hflag))
185 printf("%s:", str);
186 if (mode == 'n')
187 printf("%ld:", n);
188 puts(buf);
189#if FEATURE_GREP_CONTEXT
190 }
191#endif
192 break;
193 }
194#if FEATURE_GREP_MAX_COUNT
195 if (mval >= 0 && matches >= mval)
196 goto end;
197#endif
198 }
199#if FEATURE_GREP_CONTEXT
200 else if (Aflag > 0 || Bflag > 0) {
201 if (mode != 'c' && mode != 'l' && mode != 'q') {
202 if (after_left > 0) {
203 print_line(str, buf, n, '-');
204 after_left--;
205 last_printed_line = n;
206 }
207 if (Bflag > 0) {
208 if (before_count == (size_t)Bflag)
209 free(before_buf[before_head].str);
210 before_buf[before_head].str = estrdup(buf);
211 before_buf[before_head].line_no = n;
212 before_head = (before_head + 1) % Bflag;
213 if (before_count < (size_t)Bflag)
214 before_count++;
215 }
216 }
217 }
218#endif
219 }
220 if (mode == 'c')
221 printf("%ld\n", c);
222end:
223#if FEATURE_GREP_CONTEXT
224 if (before_buf) {
225 for (i = 0; i < (size_t)Bflag; i++)
226 free(before_buf[i].str);
227 free(before_buf);
228 }
229#endif
230 if (ferror(fp)) {
231 weprintf("%s: read error:", str);
232 result = Error;
233 }
234 return result;
235}
236
237static void
238usage(void)
239{
240 enprintf(
241 Error,
242 "usage: %s [-EFHchilnqsvwx]"
243#if FEATURE_GREP_CONTEXT
244 " [-A num] [-B num] [-C num]"
245#endif
246#if FEATURE_GREP_MAX_COUNT
247 " [-m num]"
248#endif
249 " [-e pattern] [-f file] [pattern] [file ...]\n",
250 argv0
251 );
252}
253
254// ?man grep: search files for a pattern
255// ?man arguments: pattern [file ...]
256// ?man grep searches the named input files for lines matching the given pattern
257// ?man if no files are named, or a file is -, standard input is searched
258// ?man by default, matching lines are written to standard output
259int
260main(int argc, char *argv[])
261{
262 struct pattern *pnode;
263 int m, flags = REG_NOSUB, match = NoMatch;
264 FILE *fp;
265 char *arg;
266
267 SLIST_INIT(&phead);
268
269 ARGBEGIN
270 {
271#if FEATURE_GREP_CONTEXT
272 // ?man -A:num: specify A option
273 case 'A':
274 // ?man -A num: print num lines of trailing context after each
275 // match
276 Aflag = estrtonum(EARGF(usage()), 0, LONG_MAX);
277 break;
278 // ?man -B:num: specify B option
279 case 'B':
280 // ?man -B num: print num lines of leading context before each
281 // match
282 Bflag = estrtonum(EARGF(usage()), 0, LONG_MAX);
283 break;
284 // ?man -C:num: specify C option
285 case 'C':
286 // ?man -C num: print num lines of context before and after each
287 // match; equivalent to -A num -B num
288 Aflag = Bflag = estrtonum(EARGF(usage()), 0, LONG_MAX);
289 break;
290 // ?man ARGNUM: specify RGNUM option
291 ARGNUM:
292 Aflag = Bflag = ARGNUMF();
293 break;
294#endif
295#if FEATURE_GREP_MAX_COUNT
296 // ?man -m:num: specify m option
297 case 'm':
298 // ?man -m num: stop reading a file after num matching lines
299 mval = estrtonum(EARGF(usage()), 0, LONG_MAX);
300 break;
301#endif
302 // ?man -E: specify E option
303 case 'E':
304 // ?man -E: interpret pattern as an extended regular expression
305 Eflag = 1;
306 Fflag = 0;
307 flags |= REG_EXTENDED;
308 break;
309 // ?man -F: specify F option
310 case 'F':
311 // ?man -F: interpret pattern as a list of fixed strings
312 // separated by newlines
313 Fflag = 1;
314 Eflag = 0;
315 flags &= ~REG_EXTENDED;
316 break;
317 // ?man -H: specify H option
318 case 'H':
319 // ?man -H: always print the file name with matching lines
320 Hflag = 1;
321 hflag = 0;
322 break;
323 // ?man -e:file: specify e option
324 case 'e':
325 // ?man -e pattern: specify a pattern to match; may be given
326 // multiple times
327 arg = EARGF(usage());
328 if (!(fp = fmemopen(arg, strlen(arg) + 1, "r")))
329 eprintf("fmemopen:");
330 addpatternfile(fp);
331 efshut(fp, arg);
332 eflag = 1;
333 break;
334 // ?man -f:file: specify f option
335 case 'f':
336 // ?man -f file: read patterns from file, one per line
337 arg = EARGF(usage());
338 fp = fopen(arg, "r");
339 if (!fp)
340 enprintf(Error, "fopen %s:", arg);
341 addpatternfile(fp);
342 efshut(fp, arg);
343 fflag = 1;
344 break;
345 // ?man -h: specify h option
346 case 'h':
347 // ?man -h: never print file names with matching lines
348 hflag = 1;
349 Hflag = 0;
350 break;
351 // ?man -c: specify c option
352 case 'c':
353 // ?man -c: print only a count of matching lines per file
354 /* FALLTHROUGH */
355 // ?man -l: specify l option
356 case 'l':
357 // ?man -l: print only the names of files with at least one
358 // matching line
359 /* FALLTHROUGH */
360 // ?man -n: specify n option
361 case 'n':
362 // ?man -n: prefix each matching line with its line number
363 // within its file
364 /* FALLTHROUGH */
365 // ?man -q: specify q option
366 case 'q':
367 // ?man -q: quiet mode; exit immediately with status 0 on first
368 // match and write nothing
369 mode = ARGC();
370 break;
371 // ?man -i: specify i option
372 case 'i':
373 // ?man -i: perform case-insensitive matching
374 flags |= REG_ICASE;
375 iflag = 1;
376 break;
377 // ?man -s: specify s option
378 case 's':
379 // ?man -s: suppress error messages about nonexistent or
380 // unreadable files
381 sflag = 1;
382 break;
383 // ?man -v: specify v option
384 case 'v':
385 // ?man -v: invert the sense of matching to select non-matching
386 // lines
387 vflag = 1;
388 break;
389 // ?man -w: specify w option
390 case 'w':
391 // ?man -w: match only whole words
392 wflag = 1;
393 break;
394 // ?man -x: specify x option
395 case 'x':
396 // ?man -x: match only whole lines
397 xflag = 1;
398 break;
399 default:
400 usage();
401 }
402 ARGEND
403
404 if (argc == 0 && !eflag && !fflag)
405 usage(); /* no pattern */
406
407 /* just add literal pattern to list */
408 if (!eflag && !fflag) {
409 if (!(fp = fmemopen(argv[0], strlen(argv[0]) + 1, "r")))
410 eprintf("fmemopen:");
411 addpatternfile(fp);
412 efshut(fp, argv[0]);
413 argc--;
414 argv++;
415 }
416
417 if (!Fflag)
418 /* compile regex for all search patterns */
419 SLIST_FOREACH(pnode, &phead, entry)
420 enregcomp(Error, &pnode->preg, pnode->pattern, flags);
421 many = (argc > 1);
422 if (argc == 0) {
423 match = grep(stdin, "<stdin>");
424 } else {
425 for (; *argv; argc--, argv++) {
426 if (!strcmp(*argv, "-")) {
427 *argv = "<stdin>";
428 fp = stdin;
429 } else if (!(fp = fopen(*argv, "r"))) {
430 if (!sflag)
431 weprintf("fopen %s:", *argv);
432 match = Error;
433 continue;
434 }
435 m = grep(fp, *argv);
436 if (m == Error || (match != Error && m == Match))
437 match = m;
438 if (fp != stdin && fshut(fp, *argv))
439 match = Error;
440 }
441 }
442
443 if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
444 match = Error;
445
446 // ?man
447 // ?man ## Exit status
448 // ?man
449 // ?man 0
450 // ?man : one or more lines matched in at least one file
451 // ?man
452 // ?man 1
453 // ?man : no lines matched in any file
454 // ?man
455 // ?man 2
456 // ?man : an error occurred
457 // ?man
458 // ?man ## See also
459 // ?man
460 // ?man sed(1), awk(1)
461 // ?man
462
463 return match;
464}