master xplshn/aruu / cmd / posix / grep.c
  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}