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