1140074Spjd/* $NetBSD: grep.c,v 1.16 2022/10/15 21:53:22 andvar Exp $ */ 2140074Spjd/* $FreeBSD: head/usr.bin/grep/grep.c 211519 2010-08-19 22:55:17Z delphij $ */ 3140074Spjd/* $OpenBSD: grep.c,v 1.42 2010/07/02 22:18:03 tedu Exp $ */ 4140074Spjd 5216468Sobrien/*- 6140074Spjd * Copyright (c) 1999 James Howard and Dag-Erling Co��dan Sm��rgrav 7140074Spjd * Copyright (C) 2008-2009 Gabor Kovesdan <gabor@FreeBSD.org> 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32#if HAVE_NBTOOL_CONFIG_H 33#include "nbtool_config.h" 34#endif 35 36#include <sys/cdefs.h> 37__RCSID("$NetBSD: grep.c,v 1.16 2022/10/15 21:53:22 andvar Exp $"); 38 39#include <sys/stat.h> 40#include <sys/types.h> 41 42#include <ctype.h> 43#include <err.h> 44#include <errno.h> 45#include <getopt.h> 46#include <limits.h> 47#include <libgen.h> 48#include <locale.h> 49#include <stdbool.h> 50#include <stdio.h> 51#include <stdlib.h> 52#include <string.h> 53#include <unistd.h> 54 55#include "grep.h" 56 57#ifndef WITHOUT_NLS 58#include <nl_types.h> 59nl_catd catalog; 60#endif 61 62/* 63 * Default messages to use when NLS is disabled or no catalogue 64 * is found. 65 */ 66const char *errstr[] = { 67 "", 68/* 1*/ "(standard input)", 69/* 2*/ "cannot read bzip2 compressed file", 70/* 3*/ "unknown %s option", 71/* 4*/ "usage: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A num] [-B num] [-C[num]]\n", 72/* 5*/ "\t[-e pattern] [-f file] [--binary-files=value] [--color=when]\n", 73/* 6*/ "\t[--context[=num]] [--directories=action] [--label] [--line-buffered]\n", 74/* 7*/ "\t[pattern] [file ...]\n", 75/* 8*/ "Binary file %s matches\n", 76/* 9*/ "%s (BSD grep) %s\n", 77}; 78 79/* Flags passed to regcomp() and regexec() */ 80int cflags = 0; 81int eflags = REG_STARTEND; 82 83/* Searching patterns */ 84unsigned int patterns, pattern_sz; 85char **pattern; 86regex_t *r_pattern; 87fastgrep_t *fg_pattern; 88 89/* Filename exclusion/inclusion patterns */ 90unsigned int fpatterns, fpattern_sz; 91unsigned int dpatterns, dpattern_sz; 92struct epat *dpattern, *fpattern; 93 94/* For regex errors */ 95char re_error[RE_ERROR_BUF + 1]; 96 97/* Command-line flags */ 98unsigned long long Aflag; /* -A x: print x lines trailing each match */ 99unsigned long long Bflag; /* -B x: print x lines leading each match */ 100bool Hflag; /* -H: always print file name */ 101bool Lflag; /* -L: only show names of files with no matches */ 102bool bflag; /* -b: show block numbers for each match */ 103bool cflag; /* -c: only show a count of matching lines */ 104bool hflag; /* -h: don't print filename headers */ 105bool iflag; /* -i: ignore case */ 106bool lflag; /* -l: only show names of files with matches */ 107bool mflag; /* -m x: stop reading the files after x matches */ 108unsigned long long mcount; /* count for -m */ 109bool nflag; /* -n: show line numbers in front of matching lines */ 110bool oflag; /* -o: print only matching part */ 111bool qflag; /* -q: quiet mode (don't output anything) */ 112bool sflag; /* -s: silent mode (ignore errors) */ 113bool vflag; /* -v: only show non-matching lines */ 114bool wflag; /* -w: pattern must start and end on word boundaries */ 115bool xflag; /* -x: pattern must match entire line */ 116bool lbflag; /* --line-buffered */ 117bool nullflag; /* --null */ 118bool nulldataflag; /* --null-data */ 119unsigned char line_sep = '\n'; /* 0 for --null-data */ 120char *label; /* --label */ 121const char *color; /* --color */ 122int grepbehave = GREP_BASIC; /* -EFGP: type of the regex */ 123int binbehave = BINFILE_BIN; /* -aIU: handling of binary files */ 124int filebehave = FILE_STDIO; /* -JZ: normal, gzip or bzip2 file */ 125int devbehave = DEV_READ; /* -D: handling of devices */ 126int dirbehave = DIR_READ; /* -dRr: handling of directories */ 127int linkbehave = LINK_READ; /* -OpS: handling of symlinks */ 128 129bool dexclude, dinclude; /* --exclude-dir and --include-dir */ 130bool fexclude, finclude; /* --exclude and --include */ 131 132enum { 133 BIN_OPT = CHAR_MAX + 1, 134 COLOR_OPT, 135 DECOMPRESS_OPT, 136 HELP_OPT, 137 MMAP_OPT, 138 LINEBUF_OPT, 139 LABEL_OPT, 140 R_EXCLUDE_OPT, 141 R_INCLUDE_OPT, 142 R_DEXCLUDE_OPT, 143 R_DINCLUDE_OPT 144}; 145 146static inline const char *init_color(const char *); 147 148/* Housekeeping */ 149int tail; /* lines left to print */ 150bool notfound; /* file not found */ 151 152extern char *__progname; 153 154/* 155 * Prints usage information and returns 2. 156 */ 157__dead static void 158usage(void) 159{ 160 fprintf(stderr, getstr(4), __progname); 161 fprintf(stderr, "%s", getstr(5)); 162 fprintf(stderr, "%s", getstr(6)); 163 fprintf(stderr, "%s", getstr(7)); 164 exit(2); 165} 166 167static const char optstr[] = 168 "0123456789A:B:C:D:EFGHIJLOPSRUVZabcd:e:f:hilm:nopqrsuvwxyz"; 169 170struct option long_options[] = 171{ 172 {"binary-files", required_argument, NULL, BIN_OPT}, 173#ifndef WITHOUT_GZIP 174 {"decompress", no_argument, NULL, DECOMPRESS_OPT}, 175#endif 176 {"help", no_argument, NULL, HELP_OPT}, 177 {"mmap", no_argument, NULL, MMAP_OPT}, 178 {"line-buffered", no_argument, NULL, LINEBUF_OPT}, 179 {"label", required_argument, NULL, LABEL_OPT}, 180 {"color", optional_argument, NULL, COLOR_OPT}, 181 {"colour", optional_argument, NULL, COLOR_OPT}, 182 {"exclude", required_argument, NULL, R_EXCLUDE_OPT}, 183 {"include", required_argument, NULL, R_INCLUDE_OPT}, 184 {"exclude-dir", required_argument, NULL, R_DEXCLUDE_OPT}, 185 {"include-dir", required_argument, NULL, R_DINCLUDE_OPT}, 186 {"after-context", required_argument, NULL, 'A'}, 187 {"text", no_argument, NULL, 'a'}, 188 {"before-context", required_argument, NULL, 'B'}, 189 {"byte-offset", no_argument, NULL, 'b'}, 190 {"context", optional_argument, NULL, 'C'}, 191 {"count", no_argument, NULL, 'c'}, 192 {"devices", required_argument, NULL, 'D'}, 193 {"directories", required_argument, NULL, 'd'}, 194 {"extended-regexp", no_argument, NULL, 'E'}, 195 {"regexp", required_argument, NULL, 'e'}, 196 {"fixed-strings", no_argument, NULL, 'F'}, 197 {"file", required_argument, NULL, 'f'}, 198 {"basic-regexp", no_argument, NULL, 'G'}, 199 {"no-filename", no_argument, NULL, 'h'}, 200 {"with-filename", no_argument, NULL, 'H'}, 201 {"ignore-case", no_argument, NULL, 'i'}, 202#ifndef WITHOUT_BZ2 203 {"bz2decompress", no_argument, NULL, 'J'}, 204#endif 205 {"files-with-matches", no_argument, NULL, 'l'}, 206 {"files-without-match", no_argument, NULL, 'L'}, 207 {"max-count", required_argument, NULL, 'm'}, 208 {"line-number", no_argument, NULL, 'n'}, 209 {"only-matching", no_argument, NULL, 'o'}, 210 {"quiet", no_argument, NULL, 'q'}, 211 {"silent", no_argument, NULL, 'q'}, 212 {"recursive", no_argument, NULL, 'r'}, 213 {"no-messages", no_argument, NULL, 's'}, 214 {"binary", no_argument, NULL, 'U'}, 215 {"unix-byte-offsets", no_argument, NULL, 'u'}, 216 {"invert-match", no_argument, NULL, 'v'}, 217 {"version", no_argument, NULL, 'V'}, 218 {"word-regexp", no_argument, NULL, 'w'}, 219 {"line-regexp", no_argument, NULL, 'x'}, 220 {"null", no_argument, NULL, 'Z'}, 221 {"null-data", no_argument, NULL, 'z'}, 222 {NULL, no_argument, NULL, 0} 223}; 224 225/* 226 * Adds a searching pattern to the internal array. 227 */ 228static void 229add_pattern(char *pat, size_t len) 230{ 231 232 /* TODO: Check for empty patterns and shortcut */ 233 234 /* Increase size if necessary */ 235 if (patterns == pattern_sz) { 236 pattern_sz *= 2; 237 pattern = grep_realloc(pattern, ++pattern_sz * 238 sizeof(*pattern)); 239 } 240 if (len > 0 && pat[len - 1] == '\n') 241 --len; 242 /* pat may not be NUL-terminated */ 243 pattern[patterns] = grep_malloc(len + 1); 244 memcpy(pattern[patterns], pat, len); 245 pattern[patterns][len] = '\0'; 246 ++patterns; 247} 248 249/* 250 * Adds a file include/exclude pattern to the internal array. 251 */ 252static void 253add_fpattern(const char *pat, int mode) 254{ 255 256 /* Increase size if necessary */ 257 if (fpatterns == fpattern_sz) { 258 fpattern_sz *= 2; 259 fpattern = grep_realloc(fpattern, ++fpattern_sz * 260 sizeof(struct epat)); 261 } 262 fpattern[fpatterns].pat = grep_strdup(pat); 263 fpattern[fpatterns].mode = mode; 264 ++fpatterns; 265} 266 267/* 268 * Adds a directory include/exclude pattern to the internal array. 269 */ 270static void 271add_dpattern(const char *pat, int mode) 272{ 273 274 /* Increase size if necessary */ 275 if (dpatterns == dpattern_sz) { 276 dpattern_sz *= 2; 277 dpattern = grep_realloc(dpattern, ++dpattern_sz * 278 sizeof(struct epat)); 279 } 280 dpattern[dpatterns].pat = grep_strdup(pat); 281 dpattern[dpatterns].mode = mode; 282 ++dpatterns; 283} 284 285/* 286 * Reads searching patterns from a file and adds them with add_pattern(). 287 */ 288static void 289read_patterns(const char *fn) 290{ 291 FILE *f; 292 char *line; 293 size_t len; 294 ssize_t rlen; 295 296 if ((f = fopen(fn, "r")) == NULL) 297 err(2, "%s", fn); 298 line = NULL; 299 len = 0; 300 while ((rlen = getline(&line, &len, f)) != -1) 301 add_pattern(line, *line == '\n' ? 0 : (size_t)rlen); 302 free(line); 303 if (ferror(f)) 304 err(2, "%s", fn); 305 fclose(f); 306} 307 308static inline const char * 309init_color(const char *d) 310{ 311 char *c; 312 313 c = getenv("GREP_COLOR"); 314 return (c != NULL ? c : d); 315} 316 317int 318main(int argc, char *argv[]) 319{ 320 char **aargv, **eargv, *eopts; 321 char *ep; 322 unsigned long long l; 323 unsigned int aargc, eargc, i, j; 324 int c, lastc, needpattern, newarg, prevoptind; 325 326 setlocale(LC_ALL, ""); 327 328#ifndef WITHOUT_NLS 329 catalog = catopen("grep", NL_CAT_LOCALE); 330#endif 331 332 /* Check what is the program name of the binary. In this 333 way we can have all the functionalities in one binary 334 without the need of scripting and using ugly hacks. */ 335 switch (__progname[0]) { 336 case 'e': 337 grepbehave = GREP_EXTENDED; 338 break; 339 case 'f': 340 grepbehave = GREP_FIXED; 341 break; 342 case 'g': 343 grepbehave = GREP_BASIC; 344 break; 345#ifndef WITHOUT_GZIP 346 case 'z': 347 filebehave = FILE_GZIP; 348 switch(__progname[1]) { 349 case 'e': 350 grepbehave = GREP_EXTENDED; 351 break; 352 case 'f': 353 grepbehave = GREP_FIXED; 354 break; 355 case 'g': 356 grepbehave = GREP_BASIC; 357 break; 358 } 359 break; 360#endif 361 } 362 363 lastc = '\0'; 364 newarg = 1; 365 prevoptind = 1; 366 needpattern = 1; 367 368 eopts = getenv("GREP_OPTIONS"); 369 370 /* support for extra arguments in GREP_OPTIONS */ 371 eargc = 0; 372 if (eopts != NULL) { 373 char *str; 374 375 /* make an estimation of how many extra arguments we have */ 376 for (j = 0; j < strlen(eopts); j++) 377 if (eopts[j] == ' ') 378 eargc++; 379 380 eargv = (char **)grep_malloc(sizeof(char *) * (eargc + 1)); 381 382 eargc = 0; 383 /* parse extra arguments */ 384 while ((str = strsep(&eopts, " ")) != NULL) 385 eargv[eargc++] = grep_strdup(str); 386 387 aargv = (char **)grep_calloc(eargc + argc + 1, 388 sizeof(char *)); 389 390 aargv[0] = argv[0]; 391 for (i = 0; i < eargc; i++) 392 aargv[i + 1] = eargv[i]; 393 for (j = 1; j < (unsigned int)argc; j++, i++) 394 aargv[i + 1] = argv[j]; 395 396 aargc = eargc + argc; 397 } else { 398 aargv = argv; 399 aargc = argc; 400 } 401 402 while (((c = getopt_long(aargc, aargv, optstr, long_options, NULL)) != 403 -1)) { 404 switch (c) { 405 case '0': case '1': case '2': case '3': case '4': 406 case '5': case '6': case '7': case '8': case '9': 407 if (newarg || !isdigit(lastc)) 408 Aflag = 0; 409 else if (Aflag > LLONG_MAX / 10) { 410 errno = ERANGE; 411 err(2, NULL); 412 } 413 Aflag = Bflag = (Aflag * 10) + (c - '0'); 414 break; 415 case 'C': 416 if (optarg == NULL) { 417 Aflag = Bflag = 2; 418 break; 419 } 420 /* FALLTHROUGH */ 421 case 'A': 422 /* FALLTHROUGH */ 423 case 'B': 424 errno = 0; 425 l = strtoull(optarg, &ep, 10); 426 if (((errno == ERANGE) && (l == ULLONG_MAX)) || 427 ((errno == EINVAL) && (l == 0))) 428 err(2, NULL); 429 else if (ep[0] != '\0') { 430 errno = EINVAL; 431 err(2, NULL); 432 } 433 if (c == 'A') 434 Aflag = l; 435 else if (c == 'B') 436 Bflag = l; 437 else 438 Aflag = Bflag = l; 439 break; 440 case 'a': 441 binbehave = BINFILE_TEXT; 442 break; 443 case 'b': 444 bflag = true; 445 break; 446 case 'c': 447 cflag = true; 448 break; 449 case 'D': 450 if (strcasecmp(optarg, "skip") == 0) 451 devbehave = DEV_SKIP; 452 else if (strcasecmp(optarg, "read") == 0) 453 devbehave = DEV_READ; 454 else 455 errx(2, getstr(3), "--devices"); 456 break; 457 case 'd': 458 if (strcasecmp("recurse", optarg) == 0) { 459 Hflag = true; 460 dirbehave = DIR_RECURSE; 461 } else if (strcasecmp("skip", optarg) == 0) 462 dirbehave = DIR_SKIP; 463 else if (strcasecmp("read", optarg) == 0) 464 dirbehave = DIR_READ; 465 else 466 errx(2, getstr(3), "--directories"); 467 break; 468 case 'E': 469 grepbehave = GREP_EXTENDED; 470 break; 471 case 'e': 472 add_pattern(optarg, strlen(optarg)); 473 needpattern = 0; 474 break; 475 case 'F': 476 grepbehave = GREP_FIXED; 477 break; 478 case 'f': 479 read_patterns(optarg); 480 needpattern = 0; 481 break; 482 case 'G': 483 grepbehave = GREP_BASIC; 484 break; 485 case 'H': 486 Hflag = true; 487 break; 488 case 'h': 489 Hflag = false; 490 hflag = true; 491 break; 492 case 'I': 493 binbehave = BINFILE_SKIP; 494 break; 495 case 'i': 496 case 'y': 497 iflag = true; 498 cflags |= REG_ICASE; 499 break; 500#ifndef WITHOUT_BZ2 501 case 'J': 502 filebehave = FILE_BZIP; 503 break; 504#endif 505 case 'L': 506 lflag = false; 507 Lflag = true; 508 break; 509 case 'l': 510 Lflag = false; 511 lflag = true; 512 break; 513 case 'm': 514 mflag = true; 515 errno = 0; 516 mcount = strtoull(optarg, &ep, 10); 517 if (((errno == ERANGE) && (mcount == ULLONG_MAX)) || 518 ((errno == EINVAL) && (mcount == 0))) 519 err(2, NULL); 520 else if (ep[0] != '\0') { 521 errno = EINVAL; 522 err(2, NULL); 523 } 524 break; 525 case 'n': 526 nflag = true; 527 break; 528 case 'O': 529 linkbehave = LINK_EXPLICIT; 530 break; 531 case 'o': 532 oflag = true; 533 break; 534 case 'p': 535 linkbehave = LINK_SKIP; 536 break; 537 case 'q': 538 qflag = true; 539 break; 540 case 'S': 541 linkbehave = LINK_READ; 542 break; 543 case 'R': 544 case 'r': 545 dirbehave = DIR_RECURSE; 546 Hflag = true; 547 break; 548 case 's': 549 sflag = true; 550 break; 551 case 'U': 552 binbehave = BINFILE_BIN; 553 break; 554 case 'u': 555 case MMAP_OPT: 556 /* noop, compatibility */ 557 break; 558 case 'V': 559 printf(getstr(9), __progname, VERSION); 560 exit(0); 561 case 'v': 562 vflag = true; 563 break; 564 case 'w': 565 wflag = true; 566 break; 567 case 'x': 568 xflag = true; 569 break; 570 case 'Z': 571 nullflag = true; 572 break; 573 case 'z': 574 nulldataflag = true; 575 line_sep = '\0'; 576 break; 577 case BIN_OPT: 578 if (strcasecmp("binary", optarg) == 0) 579 binbehave = BINFILE_BIN; 580 else if (strcasecmp("without-match", optarg) == 0) 581 binbehave = BINFILE_SKIP; 582 else if (strcasecmp("text", optarg) == 0) 583 binbehave = BINFILE_TEXT; 584 else 585 errx(2, getstr(3), "--binary-files"); 586 break; 587 case COLOR_OPT: 588 color = NULL; 589 if (optarg == NULL || strcasecmp("auto", optarg) == 0 || 590 strcasecmp("tty", optarg) == 0 || 591 strcasecmp("if-tty", optarg) == 0) { 592 char *term; 593 594 term = getenv("TERM"); 595 if (isatty(STDOUT_FILENO) && term != NULL && 596 strcasecmp(term, "dumb") != 0) 597 color = init_color("01;31"); 598 } else if (strcasecmp("always", optarg) == 0 || 599 strcasecmp("yes", optarg) == 0 || 600 strcasecmp("force", optarg) == 0) { 601 color = init_color("01;31"); 602 } else if (strcasecmp("never", optarg) != 0 && 603 strcasecmp("none", optarg) != 0 && 604 strcasecmp("no", optarg) != 0) 605 errx(2, getstr(3), "--color"); 606 break; 607#ifndef WITHOUT_GZIP 608 case DECOMPRESS_OPT: 609 filebehave = FILE_GZIP; 610 break; 611#endif 612 case LABEL_OPT: 613 label = optarg; 614 break; 615 case LINEBUF_OPT: 616 lbflag = true; 617 break; 618 case R_INCLUDE_OPT: 619 finclude = true; 620 add_fpattern(optarg, INCL_PAT); 621 break; 622 case R_EXCLUDE_OPT: 623 fexclude = true; 624 add_fpattern(optarg, EXCL_PAT); 625 break; 626 case R_DINCLUDE_OPT: 627 dinclude = true; 628 add_dpattern(optarg, INCL_PAT); 629 break; 630 case R_DEXCLUDE_OPT: 631 dexclude = true; 632 add_dpattern(optarg, EXCL_PAT); 633 break; 634 case HELP_OPT: 635 default: 636 usage(); 637 } 638 lastc = c; 639 newarg = optind != prevoptind; 640 prevoptind = optind; 641 } 642 aargc -= optind; 643 aargv += optind; 644 645 /* Fail if we don't have any pattern */ 646 if (aargc == 0 && needpattern) 647 usage(); 648 649 /* Process patterns from command line */ 650 if (aargc != 0 && needpattern) { 651 add_pattern(*aargv, strlen(*aargv)); 652 --aargc; 653 ++aargv; 654 } 655 656 switch (grepbehave) { 657 case GREP_FIXED: 658 case GREP_BASIC: 659 break; 660 case GREP_EXTENDED: 661 cflags |= REG_EXTENDED; 662 break; 663 default: 664 /* NOTREACHED */ 665 usage(); 666 } 667 668 fg_pattern = grep_calloc(patterns, sizeof(*fg_pattern)); 669 r_pattern = grep_calloc(patterns, sizeof(*r_pattern)); 670/* 671 * XXX: fgrepcomp() and fastcomp() are workarounds for regexec() performance. 672 * Optimizations should be done there. 673 */ 674 /* Check if cheating is allowed (always is for fgrep). */ 675 if (grepbehave == GREP_FIXED) { 676 for (i = 0; i < patterns; ++i) 677 fgrepcomp(&fg_pattern[i], pattern[i]); 678 } else { 679 for (i = 0; i < patterns; ++i) { 680 if (fastcomp(&fg_pattern[i], pattern[i])) { 681 /* Fall back to full regex library */ 682 c = regcomp(&r_pattern[i], pattern[i], cflags); 683 if (c != 0) { 684 regerror(c, &r_pattern[i], re_error, 685 RE_ERROR_BUF); 686 errx(2, "%s", re_error); 687 } 688 } 689 } 690 } 691 692 if (lbflag) { 693#ifdef _IOLBF 694 setvbuf(stdout, NULL, _IOLBF, 0); 695#else 696 setlinebuf(stdout); 697#endif 698 } 699 700 if ((aargc == 0 || aargc == 1) && !Hflag) 701 hflag = true; 702 703 if (aargc == 0) 704 exit(!procfile("-")); 705 706 if (dirbehave == DIR_RECURSE) 707 c = grep_tree(aargv); 708 else 709 for (c = 0; aargc--; ++aargv) { 710 if ((finclude || fexclude) && !file_matching(*aargv)) 711 continue; 712 c+= procfile(*aargv); 713 } 714 715#ifndef WITHOUT_NLS 716 catclose(catalog); 717#endif 718 719 /* Find out the correct return value according to the 720 results and the command line option. */ 721 exit(c ? (notfound ? (qflag ? 0 : 2) : 0) : (notfound ? 2 : 1)); 722} 723