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