agrep.c revision 1.2
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. */ 44static char const short_options[] = 45"cd:e:hiklnqsvwyBD:E:HI:MS:V0123456789-:"; 46 47static int show_help; 48char *program_name; 49 50#ifdef HAVE_GETOPT_LONG 51/* Long options that have no corresponding short equivalents. */ 52enum { 53 COLOR_OPTION = CHAR_MAX + 1, 54 SHOW_POSITION_OPTION 55}; 56 57/* Long option equivalences. */ 58static 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 92tre_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(_("\ 105Searches for approximate matches of PATTERN in each FILE or standard input.\n\ 106Example: `%s -2 optimize foo.txt' outputs all lines in file `foo.txt' that\n\ 107match \"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(_("\ 111Regexp 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\ 117Approximate 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\ 125Miscellaneous:\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\ 133Output 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 \ 145strings\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(_("\ 150With no FILE, or when FILE is -, reads standard input. If less than two\n\ 151FILEs are given, -h is assumed. Exit status is 0 if a match is found, 1 for\n\ 152no match, and 2 if there were errors. If -E or -# is not specified, only\n\ 153exact matches are selected.\n")); 154 printf("\n"); 155 printf(_("\ 156PATTERN is a POSIX extended regular expression (ERE) with the TRE extensions.\n\ 157See 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 165static regex_t preg; /* Compiled pattern to search for. */ 166static regex_t delim; /* Compiled record delimiter pattern. */ 167 168#define INITIAL_BUF_SIZE 10240 /* Initial size of the buffer. */ 169static char *buf; /* Buffer for scanning text. */ 170static int buf_size; /* Current size of the buffer. */ 171static int data_len; /* Amount of data in the buffer. */ 172static char *record; /* Start of current record. */ 173static char *next_record; /* Start of next record. */ 174static int record_len; /* Length of current record. */ 175static int delim_len; /* Length of delimiter before record. */ 176static int next_delim_len; /* Length of delimiter after record. */ 177static int delim_after = 1;/* If true, print the delimiter after the record. */ 178static int at_eof; 179static int have_matches; /* If true, matches have been found. */ 180 181static int invert_match; /* Show only non-matching records. */ 182static int print_filename; /* Output filename. */ 183static int print_recnum; /* Output record number. */ 184static int print_cost; /* Output match cost. */ 185static int count_matches; /* Count matching records. */ 186static int list_files; /* List matching files. */ 187static int color_option; /* Highlight matches. */ 188static int print_position; /* Show start and end offsets for matches. */ 189 190static int best_match; /* Output only best matches. */ 191static int best_cost; /* Best match cost found so far. */ 192static int be_silent; /* Never output anything */ 193 194static 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. */ 198static 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. */ 203static inline int 204tre_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 311static int 312tre_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 468int 469main(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(_("\ 601Copyright (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