xref: /netbsd-src/external/bsd/tre/dist/src/agrep.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
1 /*
2   agrep.c - Approximate grep
3 
4   This software is released under a BSD-style license.
5   See the file LICENSE for details and copyright.
6 
7 */
8 
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 #endif /* HAVE_CONFIG_H */
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <locale.h>
15 #include <string.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <fcntl.h>
19 #include <errno.h>
20 #include <assert.h>
21 #include <limits.h>
22 #include <unistd.h>
23 #ifdef HAVE_GETOPT_H
24 #include <getopt.h>
25 #endif /* HAVE_GETOPT_H */
26 #include "regex.h"
27 
28 #ifdef HAVE_GETTEXT
29 #include <libintl.h>
30 #else
31 #define gettext(s) s
32 #define bindtextdomain(p, d)
33 #define textdomain(p)
34 #endif
35 
36 #define _(String) gettext(String)
37 
38 #undef MAX
39 #undef MIN
40 #define MAX(a, b) (((a) >= (b)) ? (a) : (b))
41 #define MIN(a, b) (((a) <= (b)) ? (a) : (b))
42 
43 /* Short options. */
44 static char const short_options[] =
45 "cd:e:hiklnqsvwyBD:E:HI:MS:V0123456789-:";
46 
47 static int show_help;
48 char *program_name;
49 
50 #ifdef HAVE_GETOPT_LONG
51 /* Long options that have no corresponding short equivalents. */
52 enum {
53   COLOR_OPTION = CHAR_MAX + 1,
54   SHOW_POSITION_OPTION
55 };
56 
57 /* Long option equivalences. */
58 static struct option const long_options[] =
59 {
60   {"best-match", no_argument, NULL, 'B'},
61   {"color", no_argument, NULL, COLOR_OPTION},
62   {"colour", no_argument, NULL, COLOR_OPTION},
63   {"count", no_argument, NULL, 'c'},
64   {"delete-cost", required_argument, NULL, 'D'},
65   {"delimiter", no_argument, NULL, 'd'},
66   {"delimiter-after", no_argument, NULL, 'M'},
67   {"files-with-matches", no_argument, NULL, 'l'},
68   {"help", no_argument, &show_help, 1},
69   {"ignore-case", no_argument, NULL, 'i'},
70   {"insert-cost", required_argument, NULL, 'I'},
71   {"invert-match", no_argument, NULL, 'v'},
72   {"line-number", no_argument, NULL, 'n'},
73   {"literal", no_argument, NULL, 'k'},
74   {"max-errors", required_argument, NULL, 'E'},
75   {"no-filename", no_argument, NULL, 'h'},
76   {"nothing", no_argument, NULL, 'y'},
77   {"quiet", no_argument, NULL, 'q'},
78   {"record-number", no_argument, NULL, 'n'},
79   {"regexp", required_argument, NULL, 'e'},
80   {"show-cost", no_argument, NULL, 's'},
81   {"show-position", no_argument, NULL, SHOW_POSITION_OPTION},
82   {"silent", no_argument, NULL, 'q'},
83   {"substitute-cost", required_argument, NULL, 'S'},
84   {"version", no_argument, NULL, 'V'},
85   {"with-filename", no_argument, NULL, 'H'},
86   {"word-regexp", no_argument, NULL, 'w'},
87   {0, 0, 0, 0}
88 };
89 #endif /* HAVE_GETOPT_LONG */
90 
91 __dead static void
92 tre_agrep_usage(int status)
93 {
94   if (status != 0)
95     {
96       fprintf(stderr, _("Usage: %s [OPTION]... PATTERN [FILE]...\n"),
97 	      program_name);
98       fprintf(stderr, _("Try `%s --help' for more information.\n"),
99               program_name);
100     }
101   else
102     {
103       printf(_("Usage: %s [OPTION]... PATTERN [FILE]...\n"), program_name);
104       printf(_("\
105 Searches for approximate matches of PATTERN in each FILE or standard input.\n\
106 Example: `%s -2 optimize foo.txt' outputs all lines in file `foo.txt' that\n\
107 match \"optimize\" within two errors.  E.g. lines which contain \"optimise\",\n\
108 \"optmise\", and \"opitmize\" all match.\n"), program_name);
109       printf("\n");
110       printf(_("\
111 Regexp selection and interpretation:\n\
112   -e, --regexp=PATTERN	    use PATTERN as a regular expression\n\
113   -i, --ignore-case	    ignore case distinctions\n\
114   -k, --literal		    PATTERN is a literal string\n\
115   -w, --word-regexp	    force PATTERN to match only whole words\n\
116 \n\
117 Approximate matching settings:\n\
118   -D, --delete-cost=NUM	    set cost of missing characters\n\
119   -I, --insert-cost=NUM	    set cost of extra characters\n\
120   -S, --substitute-cost=NUM set cost of wrong characters\n\
121   -E, --max-errors=NUM	    select records that have at most NUM errors\n\
122   -#			    select records that have at most # errors (# is a\n\
123 			    digit between 0 and 9)\n\
124 \n\
125 Miscellaneous:\n\
126   -d, --delimiter=PATTERN   set the record delimiter regular expression\n\
127   -v, --invert-match	    select non-matching records\n\
128   -V, --version		    print version information and exit\n\
129   -y, --nothing		    does nothing (for compatibility with the non-free\n\
130 			    agrep program)\n\
131       --help		    display this help and exit\n\
132 \n\
133 Output control:\n\
134   -B, --best-match	    only output records with least errors\n\
135   -c, --count		    only print a count of matching records per FILE\n\
136   -h, --no-filename	    suppress the prefixing filename on output\n\
137   -H, --with-filename	    print the filename for each match\n\
138   -l, --files-with-matches  only print FILE names containing matches\n\
139   -M, --delimiter-after     print record delimiter after record if -d is used\n\
140   -n, --record-number	    print record number with output\n\
141       --line-number         same as -n\n\
142   -q, --quiet, --silent	    suppress all normal output\n\
143   -s, --show-cost	    print match cost with output\n\
144       --colour, --color     use markers to distinguish the matching \
145 strings\n\
146       --show-position       prefix each output record with start and end\n\
147                             position of the first match within the record\n"));
148       printf("\n");
149       printf(_("\
150 With no FILE, or when FILE is -, reads standard input.  If less than two\n\
151 FILEs are given, -h is assumed.  Exit status is 0 if a match is found, 1 for\n\
152 no match, and 2 if there were errors.  If -E or -# is not specified, only\n\
153 exact matches are selected.\n"));
154       printf("\n");
155       printf(_("\
156 PATTERN is a POSIX extended regular expression (ERE) with the TRE extensions.\n\
157 See tre(7) for a complete description.\n"));
158       printf("\n");
159       printf(_("Report bugs to: "));
160       printf("%s.\n", PACKAGE_BUGREPORT);
161     }
162   exit(status);
163 }
164 
165 static regex_t preg;	  /* Compiled pattern to search for. */
166 static regex_t delim;	  /* Compiled record delimiter pattern. */
167 
168 #define INITIAL_BUF_SIZE 10240	/* Initial size of the buffer. */
169 static char *buf;	   /* Buffer for scanning text. */
170 static int buf_size;	   /* Current size of the buffer. */
171 static int data_len;	   /* Amount of data in the buffer. */
172 static char *record;	   /* Start of current record. */
173 static char *next_record;  /* Start of next record. */
174 static int record_len;	   /* Length of current record. */
175 static int delim_len;      /* Length of delimiter before record. */
176 static int next_delim_len; /* Length of delimiter after record. */
177 static int delim_after = 1;/* If true, print the delimiter after the record. */
178 static int at_eof;
179 static int have_matches;   /* If true, matches have been found. */
180 
181 static int invert_match;   /* Show only non-matching records. */
182 static int print_filename; /* Output filename. */
183 static int print_recnum;   /* Output record number. */
184 static int print_cost;	   /* Output match cost. */
185 static int count_matches;  /* Count matching records. */
186 static int list_files;	   /* List matching files. */
187 static int color_option;   /* Highlight matches. */
188 static int print_position;  /* Show start and end offsets for matches. */
189 
190 static int best_match;	     /* Output only best matches. */
191 static int best_cost;	     /* Best match cost found so far. */
192 static int be_silent;	     /* Never output anything */
193 
194 static regaparams_t match_params;
195 
196 /* The color string used with the --color option.  If set, the
197    environment variable GREP_COLOR overrides this default value. */
198 static const char *highlight = "01;31";
199 
200 /* Sets `record' to the next complete record from file `fd', and `record_len'
201    to the length of the record.	 Returns 1 when there are no more records,
202    0 otherwise. */
203 static inline int
204 tre_agrep_get_next_record(int fd, const char *filename)
205 {
206   if (at_eof)
207     return 1;
208 
209   while (1)
210     {
211       int errcode;
212       regmatch_t pmatch[1];
213 
214       if (next_record == NULL)
215 	{
216 	  int r;
217 	  int read_size = buf_size - data_len;
218 
219 	  if (read_size <= 0)
220 	    {
221 	      /* The buffer is full and no record delimiter found yet,
222 		 we need to grow the buffer.  We double the size to
223 		 avoid rescanning the data too many times when the
224 		 records are very large. */
225 	      buf_size *= 2;
226 	      buf = realloc(buf, buf_size);
227 	      if (buf == NULL)
228 		{
229 		  fprintf(stderr, "%s: %s\n", program_name, _("Out of memory"));
230 		  exit(2);
231 		}
232 	      read_size = buf_size - data_len;
233 	    }
234 
235 	  r = read(fd, buf + data_len, read_size);
236 	  if (r < 0)
237 	    {
238 	      /* Read error. */
239 	      char *err;
240 	      if (errno == EINTR)
241 		continue;
242 	      err = strerror(errno);
243 	      fprintf(stderr, "%s: ", program_name);
244 	      fprintf(stderr, _("Error reading from %s: %s\n"), filename, err);
245 	      return 1;
246 	    }
247 
248 	  if (r == 0)
249 	    {
250 	      /* End of file.  Return the last record. */
251 	      record = buf;
252 	      record_len = data_len;
253 	      at_eof = 1;
254 	      /* The empty string after a trailing delimiter is not considered
255 		 to be a record. */
256 	      if (record_len == 0)
257 		return 1;
258 	      return 0;
259 	    }
260 	  data_len += r;
261 	  next_record = buf;
262 	}
263 
264       /* Find the next record delimiter. */
265       errcode = tre_regnexec(&delim, next_record, data_len - (next_record - buf),
266 			 1, pmatch, 0);
267 
268 
269       switch (errcode)
270 	{
271 	case REG_OK:
272 	  /* Record delimiter found, now we know how long the current
273 	     record is. */
274 	  record = next_record;
275 	  record_len = pmatch[0].rm_so;
276 	  delim_len = next_delim_len;
277 
278 	  next_delim_len = pmatch[0].rm_eo - pmatch[0].rm_so;
279 	  next_record = next_record + pmatch[0].rm_eo;
280 	  return 0;
281 	  break;
282 
283 	case REG_NOMATCH:
284 	  if (next_record == buf)
285 	    {
286 	      next_record = NULL;
287 	      continue;
288 	    }
289 
290 	  /* Move the data to start of the buffer and read more
291 	     data. */
292 	  memmove(buf, next_record, buf + data_len - next_record);
293 	  data_len = buf + data_len - next_record;
294 	  next_record = NULL;
295 	  continue;
296 	  break;
297 
298 	case REG_ESPACE:
299 	  fprintf(stderr, "%s: %s\n", program_name, _("Out of memory"));
300 	  exit(2);
301 	  break;
302 
303 	default:
304 	  assert(0);
305 	  break;
306 	}
307     }
308 }
309 
310 
311 static int
312 tre_agrep_handle_file(const char *filename)
313 {
314   int fd;
315   int count = 0;
316   int recnum = 0;
317 
318   /* Allocate the initial buffer. */
319   if (buf == NULL)
320     {
321       buf = malloc(INITIAL_BUF_SIZE);
322       if (buf == NULL)
323 	{
324 	  fprintf(stderr, "%s: %s\n", program_name, _("Out of memory"));
325 	  exit(2);
326 	}
327       buf_size = INITIAL_BUF_SIZE;
328     }
329 
330   /* Reset read buffer state. */
331   next_record = NULL;
332   data_len = 0;
333 
334   if (!filename || strcmp(filename, "-") == 0)
335     {
336       if (best_match)
337 	{
338 	  fprintf(stderr, "%s: %s\n", program_name,
339 		  _("Cannot use -B when reading from standard input."));
340 	  return 2;
341 	}
342       fd = 0;
343       filename = _("(standard input)");
344     }
345   else
346     {
347       fd = open(filename, O_RDONLY);
348     }
349 
350   if (fd < 0)
351     {
352       fprintf(stderr, "%s: %s: %s\n", program_name, filename, strerror(errno));
353       return 1;
354     }
355 
356 
357   /* Go through all records and output the matching ones, or the non-matching
358      ones if `invert_match' is true. */
359   at_eof = 0;
360   while (!tre_agrep_get_next_record(fd, filename))
361     {
362       int errcode;
363       regamatch_t match;
364       regmatch_t pmatch[1];
365       recnum++;
366       memset(&match, 0, sizeof(match));
367       if (best_match)
368 	match_params.max_cost = best_cost;
369       if (color_option || print_position)
370 	{
371 	  match.pmatch = pmatch;
372 	  match.nmatch = 1;
373 	}
374 
375       /* Stop searching for better matches if an exact match is found. */
376       if (best_match == 1 && best_cost == 0)
377 	break;
378 
379       /* See if the record matches. */
380       errcode = tre_reganexec(&preg, record, record_len, &match, match_params, 0);
381       if ((!invert_match && errcode == REG_OK)
382 	  || (invert_match && errcode == REG_NOMATCH))
383 	{
384 	  if (be_silent)
385 	    exit(0);
386 
387 	  count++;
388 	  have_matches = 1;
389 	  if (best_match)
390 	    {
391 	      if (best_match == 1)
392 		{
393 		  /* First best match pass. */
394 		  if (match.cost < best_cost)
395 		    best_cost = match.cost;
396 		  continue;
397 		}
398 	      /* Second best match pass. */
399 	      if (match.cost > best_cost)
400 		continue;
401 	    }
402 
403 	  if (list_files)
404 	    {
405 	      printf("%s\n", filename);
406 	      break;
407 	    }
408 	  else if (!count_matches)
409 	    {
410 	      if (print_filename)
411 		printf("%s:", filename);
412 	      if (print_recnum)
413 		printf("%d:", recnum);
414 	      if (print_cost)
415 		printf("%d:", match.cost);
416 	      if (print_position)
417 		printf("%d-%d:",
418 		       invert_match ? 0 : (int)pmatch[0].rm_so,
419 		       invert_match ? record_len : (int)pmatch[0].rm_eo);
420 
421 	      /* Adjust record boundaries so we print the delimiter
422 		 before or after the record. */
423 	      if (delim_after)
424 		{
425 		  record_len += next_delim_len;
426 		}
427 	      else
428 		{
429 		  record -= delim_len;
430 		  record_len += delim_len;
431 		  pmatch[0].rm_so += delim_len;
432 		  pmatch[0].rm_eo += delim_len;
433 		}
434 
435 	      if (color_option && !invert_match)
436 		{
437 		  printf("%.*s", (int)pmatch[0].rm_so, record);
438 		  printf("\33[%sm", highlight);
439 		  printf("%.*s", (int)(pmatch[0].rm_eo - pmatch[0].rm_so),
440 			 record + pmatch[0].rm_so);
441 		  fputs("\33[00m", stdout);
442 		  printf("%.*s", (int)(record_len - pmatch[0].rm_eo),
443 			 record + pmatch[0].rm_eo);
444 		}
445 	      else
446 		{
447 		  printf("%.*s", record_len, record);
448 		}
449 	    }
450 	}
451     }
452 
453   if (count_matches && !best_match && !be_silent)
454     {
455       if (print_filename)
456 	printf("%s:", filename);
457       printf("%d\n", count);
458     }
459 
460   if (fd)
461     close(fd);
462 
463   return 0;
464 }
465 
466 
467 
468 int
469 main(int argc, char **argv)
470 {
471   int c, errcode;
472   int comp_flags = REG_EXTENDED;
473   char *tmp_str;
474   char *regexp = NULL;
475   const char *delim_regexp = "\n";
476   int word_regexp = 0;
477   int literal_string = 0;
478   int max_cost_set = 0;
479 
480   setlocale (LC_ALL, "");
481   bindtextdomain (PACKAGE, LOCALEDIR);
482   textdomain (PACKAGE);
483 
484   /* Get the program name without the path (for error messages etc). */
485   program_name = argv[0];
486   if (program_name)
487     {
488       tmp_str = strrchr(program_name, '/');
489       if (tmp_str)
490 	program_name = tmp_str + 1;
491     }
492 
493   /* Defaults. */
494   print_filename = -1;
495   print_cost = 0;
496   be_silent = 0;
497   tre_regaparams_default(&match_params);
498   match_params.max_cost = 0;
499 
500   /* Parse command line options. */
501   while (1)
502     {
503 #ifdef HAVE_GETOPT_LONG
504       c = getopt_long(argc, argv, short_options, long_options, NULL);
505 #else /* !HAVE_GETOPT_LONG */
506       c = getopt(argc, argv, short_options);
507 #endif /* !HAVE_GETOPT_LONG */
508       if (c == -1)
509 	break;
510 
511       switch (c)
512 	{
513 	case 'c':
514 	  /* Count number of matching records. */
515 	  count_matches = 1;
516 	  break;
517 	case 'd':
518 	  /* Set record delimiter regexp. */
519 	  delim_regexp = optarg;
520 	  if (delim_after == 1)
521 	    delim_after = 0;
522 	  break;
523 	case 'e':
524 	  /* Regexp to use. */
525 	  regexp = optarg;
526 	  break;
527 	case 'h':
528 	  /* Don't prefix filename on output if there are multiple files. */
529 	  print_filename = 0;
530 	  break;
531 	case 'i':
532 	  /* Ignore case. */
533 	  comp_flags |= REG_ICASE;
534 	  break;
535 	case 'k':
536 	  /* The pattern is a literal string. */
537 	  literal_string = 1;
538 	  break;
539 	case 'l':
540 	  /* Only print files that contain matches. */
541 	  list_files = 1;
542 	  break;
543 	case 'n':
544 	  /* Print record number of matching record. */
545 	  print_recnum = 1;
546 	  break;
547 	case 'q':
548 	  be_silent = 1;
549 	  break;
550 	case 's':
551 	  /* Print match cost of matching record. */
552 	  print_cost = 1;
553 	  break;
554 	case 'v':
555 	  /* Select non-matching records. */
556 	  invert_match = 1;
557 	  break;
558 	case 'w':
559 	  /* Match only whole words. */
560 	  word_regexp = 1;
561 	  break;
562 	case 'y':
563 	  /* Compatibility option, does nothing. */
564 	  break;
565 	case 'B':
566 	  /* Select only the records which have the best match. */
567 	  best_match = 1;
568 	  break;
569 	case 'D':
570 	  /* Set the cost of a deletion. */
571 	  match_params.cost_del = atoi(optarg);
572 	  break;
573 	case 'E':
574 	  /* Set the maximum number of errors allowed for a record to match. */
575 	  match_params.max_cost = atoi(optarg);
576 	  max_cost_set = 1;
577 	  break;
578 	case 'H':
579 	  /* Always print filename prefix on output. */
580 	  print_filename = 1;
581 	  break;
582 	case 'I':
583 	  /* Set the cost of an insertion. */
584 	  match_params.cost_ins = atoi(optarg);
585 	  break;
586 	case 'M':
587 	  /* Print delimiters after matches instead of before. */
588 	  delim_after = 2;
589 	  break;
590 	case 'S':
591 	  /* Set the cost of a substitution. */
592 	  match_params.cost_subst = atoi(optarg);
593 	  break;
594 	case 'V':
595 	  {
596 	    /* Print version string and exit. */
597 	    char *version;
598 	    tre_config(TRE_CONFIG_VERSION, &version);
599 	    printf("%s (TRE agrep) %s\n\n", program_name, version);
600 	    printf(_("\
601 Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>.\n"));
602 	    printf("\n");
603 	    exit(0);
604 	    break;
605 	  }
606 	case '?':
607 	  /* Ambiguous match or extraneous parameter. */
608 	  break;
609 
610 	case '-':
611 	  /* Emulate some long options on systems which don't
612 	     have getopt_long. */
613 	  if (strcmp(optarg, "color") == 0
614 	      || strcmp(optarg, "colour") == 0)
615 	    color_option = 1;
616 	  else if (strcmp(optarg, "show-position") == 0)
617 	    print_position = 1;
618 	  else if (strcmp(optarg, "help") == 0)
619 	    show_help = 1;
620 	  else
621 	    {
622 	      fprintf(stderr, _("%s: invalid option --%s\n"),
623 		      program_name, optarg);
624 	      exit(2);
625 	    }
626 	  break;
627 
628 #ifdef HAVE_GETOPT_LONG
629 	case COLOR_OPTION:
630 	  color_option = 1;
631 	  break;
632 	case SHOW_POSITION_OPTION:
633 	  print_position = 1;
634 	  break;
635 #endif /* HAVE_GETOPT_LONG */
636 	case 0:
637 	  /* Long options without corresponding short options. */
638 	  break;
639 
640 	default:
641 	  if (c >= '0' && c <= '9')
642 	    match_params.max_cost = c - '0';
643 	  else
644 	    tre_agrep_usage(2);
645 	  max_cost_set = 1;
646 	  break;
647 	}
648     }
649 
650   if (show_help)
651     tre_agrep_usage(0);
652 
653   if (color_option)
654     {
655       char *user_highlight = getenv("GREP_COLOR");
656       if (user_highlight && *user_highlight != '\0')
657 	highlight = user_highlight;
658     }
659 
660   /* Get the pattern. */
661   if (regexp == NULL)
662     {
663       if (optind >= argc)
664 	tre_agrep_usage(2);
665       regexp = argv[optind++];
666     }
667 
668   /* If -k is specified, make the regexp literal.  This uses
669      the \Q and \E extensions.	If the string already contains
670      occurrences of \E, we need to handle them separately.  This is a
671      pain, but can't really be avoided if we want to create a regexp
672      which works together with -w (see below). */
673   if (literal_string)
674     {
675       char *next_pos = regexp;
676       char *new_re, *new_re_end;
677       int n = 0;
678       int len;
679 
680       next_pos = regexp;
681       while (next_pos)
682 	{
683 	  next_pos = strstr(next_pos, "\\E");
684 	  if (next_pos)
685 	    {
686 	      n++;
687 	      next_pos += 2;
688 	    }
689 	}
690 
691       len = strlen(regexp);
692       new_re = malloc(len + 5 + n * 7);
693       if (!new_re)
694 	{
695 	  fprintf(stderr, "%s: %s\n", program_name, _("Out of memory"));
696 	  return 2;
697 	}
698 
699       next_pos = regexp;
700       new_re_end = new_re;
701       strcpy(new_re_end, "\\Q");
702       new_re_end += 2;
703       while (next_pos)
704 	{
705 	  char *start = next_pos;
706 	  next_pos = strstr(next_pos, "\\E");
707 	  if (next_pos)
708 	    {
709 	      strncpy(new_re_end, start, next_pos - start);
710 	      new_re_end += next_pos - start;
711 	      strcpy(new_re_end, "\\E\\\\E\\Q");
712 	      new_re_end += 7;
713 	      next_pos += 2;
714 	    }
715 	  else
716 	    {
717 	      strcpy(new_re_end, start);
718 	      new_re_end += strlen(start);
719 	    }
720 	}
721       strcpy(new_re_end, "\\E");
722       regexp = new_re;
723     }
724 
725   /* If -w is specified, prepend beginning-of-word and end-of-word
726      assertions to the regexp before compiling. */
727   if (word_regexp)
728     {
729       char *tmp = regexp;
730       int len = strlen(tmp);
731       regexp = malloc(len + 7);
732       if (regexp == NULL)
733 	{
734 	  fprintf(stderr, "%s: %s\n", program_name, _("Out of memory"));
735 	  return 2;
736 	}
737       strcpy(regexp, "\\<(");
738       strcpy(regexp + 3, tmp);
739       strcpy(regexp + len + 3, ")\\>");
740     }
741 
742   /* Compile the pattern. */
743   errcode = tre_regcomp(&preg, regexp, comp_flags);
744   if (errcode)
745     {
746       char errbuf[256];
747       tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
748       fprintf(stderr, "%s: %s: %s\n",
749 	      program_name, _("Error in search pattern"), errbuf);
750       return 2;
751     }
752 
753   /* Compile the record delimiter pattern. */
754   errcode = tre_regcomp(&delim, delim_regexp, REG_EXTENDED | REG_NEWLINE);
755   if (errcode)
756     {
757       char errbuf[256];
758       tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
759       fprintf(stderr, "%s: %s: %s\n",
760 	      program_name, _("Error in record delimiter pattern"), errbuf);
761       return 2;
762     }
763 
764   if (tre_regexec(&delim, "", 0, NULL, 0) == REG_OK)
765     {
766       fprintf(stderr, "%s: %s\n", program_name,
767 	      _("Record delimiter pattern must not match an empty string"));
768       return 2;
769     }
770 
771   /* The rest of the arguments are file(s) to match. */
772 
773   /* If -h or -H were not specified, print filenames if there are more
774      than one files specified. */
775   if (print_filename == -1)
776     {
777       if (argc - optind <= 1)
778 	print_filename = 0;
779       else
780 	print_filename = 1;
781     }
782 
783   if (optind >= argc)
784     {
785       /* There are no files specified, read from stdin. */
786       tre_agrep_handle_file(NULL);
787     }
788   else if (best_match)
789     {
790       int first_ind = optind;
791 
792       /* Best match mode.  Set up the limits first. */
793       if (!max_cost_set)
794 	match_params.max_cost = INT_MAX;
795       best_cost = INT_MAX;
796 
797       /* Scan all files once without outputting anything, searching
798 	 for the best matches. */
799       while (optind < argc)
800 	tre_agrep_handle_file(argv[optind++]);
801 
802       /* If there were no matches, bail out now. */
803       if (best_cost == INT_MAX)
804 	return 1;
805 
806       /* Otherwise, rescan the files with max_cost set to the cost
807 	 of the best match found previously, this time outputting
808 	 the matches. */
809       match_params.max_cost = best_cost;
810       best_match = 2;
811       optind = first_ind;
812       while (optind < argc)
813 	tre_agrep_handle_file(argv[optind++]);
814     }
815   else
816     {
817       /* Normal mode. */
818       while (optind < argc)
819 	tre_agrep_handle_file(argv[optind++]);
820     }
821 
822   return have_matches == 0;
823 }
824