NetBSD/gnu/usr.bin/fgrep/fgrep.c

628 lines
13 KiB
C

/* fgrep.c - grep program built around matcher.
Copyright 1989 Free Software Foundation
Written August 1989 by Mike Haertel.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
The author may be reached (Email) at the address mike@ai.mit.edu,
or (US mail) as Mike Haertel c/o Free Software Foundation. */
#include "std.h"
#include "unix.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "kwset.h"
#define NCHAR (UCHAR_MAX + 1)
/* For error messages. */
static const char *prog;
static int error_seen;
/* Flags controlling the style of output. */
static int out_silent; /* Suppress all normal output. */
static int out_invert; /* Print nonmatching stuff. */
static int out_file; /* Print filenames. */
static int out_line; /* Print line numbers. */
static int out_byte; /* Print byte offsets. */
static int out_before; /* Lines of leading context. */
static int out_after; /* Lines of trailing context. */
/* Print MESG and possibly the error string for ERRNUM. Remember
that something awful happened. */
static void
DEFUN(error, (mesg, errnum), const char *mesg AND int errnum)
{
if (errnum)
fprintf(stderr, "%s: %s: %s\n", prog, mesg, strerror(errnum));
else
fprintf(stderr, "%s: %s\n", prog, mesg);
error_seen = 1;
}
/* Like error(), but die horribly after printing. */
static void
DEFUN(fatal, (mesg, errnum), const char *mesg AND int errnum)
{
error(mesg, errnum);
exit(2);
}
/* Interface to handle errors and fix library lossage. */
static PTR
DEFUN(xmalloc, (size), size_t size)
{
PTR result;
result = malloc(size);
if (size && !result)
fatal("memory exhausted", 0);
return result;
}
/* Interface to handle errors and fix some library lossage. */
static PTR
DEFUN(xrealloc, (ptr, size), PTR ptr AND size_t size)
{
PTR result;
if (ptr)
result = realloc(ptr, size);
else
result = malloc(size);
if (size && !result)
fatal("memory exhausted", 0);
return result;
}
/* Compiled search pattern. */
kwset_t kwset;
/* Flags controlling how pattern matching is performed. */
static int match_fold; /* Fold all letters to one case. */
static int match_words; /* Match only whole words. */
static int match_lines; /* Match only whole lines. */
static void
DEFUN(compile, (pattern, size), const char *pattern AND size_t size)
{
const char *beg, *lim, *err;
static char trans[NCHAR];
int i;
if (match_fold)
for (i = 0; i < NCHAR; ++i)
trans[i] = TOLOWER(i);
if (!(kwset = kwsalloc(match_fold ? trans : (const char *) NULL)))
fatal("memory exhausted", 0);
beg = pattern;
do
{
for (lim = beg; lim < pattern + size && *lim != '\n'; ++lim)
;
if (err = kwsincr(kwset, beg, lim - beg))
fatal(err, 0);
if (lim < pattern + size)
++lim;
beg = lim;
}
while (beg < pattern + size);
if (err = kwsprep(kwset))
fatal(err, 0);
}
static char *
DEFUN(execute, (buf, size), char *buf AND size_t size)
{
register char *beg, *try;
register size_t len;
struct kwsmatch kwsmatch;
beg = buf;
for (;beg <= buf + size; ++beg)
{
if (!(beg = kwsexec(kwset, beg, buf + size - beg, &kwsmatch)))
return NULL;;
len = kwsmatch.size[0];
if (match_lines)
{
if (beg > buf && beg[-1] != '\n')
continue;
if (beg + len < buf + size && *(beg + len) != '\n')
continue;
return beg;
}
else if (match_words)
for (try = beg; len && try;)
{
if (try > buf && (ISALNUM((unsigned char) try[-1])
|| !ISALNUM((unsigned char) *try)))
goto retry;
if (try + len < buf + size
&& (ISALNUM((unsigned char) *(try + len))
|| !ISALNUM((unsigned char) (try + len)[-1])))
goto retry;
return try;
retry:
if (--len)
try = kwsexec(kwset, beg, len, &kwsmatch);
else
break;
len = kwsmatch.size[0];
}
else
return beg;
}
return NULL;
}
/* Hairy buffering mechanism to efficiently support all the options. */
static char *bufbeg; /* Beginning of user-visible portion. */
static char *buflim; /* Limit of user-visible portion. */
static char *buf; /* Pointer to base of buffer. */
static size_t bufalloc; /* Allocated size of buffer. */
static size_t bufcc; /* Count of characters in buffer. */
static unsigned long int buftotalcc;
/* Total character count since reset. */
static char *buflast; /* Pointer after last character printed. */
static int bufgap; /* Weird flag indicating buflast is a lie. */
static unsigned long int buftotalnl;
/* Count of newlines before last character. */
static int bufpending; /* Lines of pending output at buflast. */
static int bufdesc; /* File descriptor to read from. */
static int bufeof; /* Flag indicating EOF reached. */
static const char *buffile; /* File name for messages. */
/* Scan and count the newlines prior to LIM in the buffer. */
static void
DEFUN(nlscan, (lim), register char *lim)
{
register char *p;
for (p = buflast; p < lim; ++p)
if (*p == '\n')
++buftotalnl;
buflast = lim;
}
/* Print the line beginning at BEG, using SEP to separate optional label
fields from the text of the line. Return the size of the line. */
static size_t
DEFUN(prline, (beg, sep), register char *beg AND register char sep)
{
register size_t cc;
register char c;
static int err;
cc = 0;
if (out_silent || err)
while (beg < buflim)
{
++cc;
if (*beg++ == '\n')
break;
}
else
{
if (out_file)
printf("%s%c", buffile, sep);
if (out_line)
{
nlscan(beg);
printf("%d%c", buftotalnl + 1, sep);
}
if (out_byte)
printf("%lu%c", buftotalcc + (beg - buf), sep);
while (beg < buflim)
{
++cc;
c = *beg++;
putchar(c);
if (c == '\n')
break;
}
if (ferror(stdout))
{
error("output error", errno);
err = 1;
}
}
if (out_line)
nlscan(beg);
else
buflast = beg;
bufgap = 0;
return cc;
}
/* Print pending bytes of last trailing context prior to LIM. */
static void
DEFUN(prpending, (lim), register char *lim)
{
while (buflast < lim && bufpending)
{
--bufpending;
prline(buflast, '-');
}
}
/* Print the lines between BEG and LIM. Deal with context crap.
Return the count of lines between BEG and LIM. */
static int
DEFUN(prtext, (beg, lim), char *beg AND char *lim)
{
static int used;
register char *p;
int i, n;
prpending(beg);
p = beg;
for (i = 0; i < out_before; ++i)
if (p > buflast)
do
--p;
while (p > buflast && p[-1] != '\n');
if ((out_before || out_after) && used && (p > buflast || bufgap))
puts("--");
while (p < beg)
p += prline(p, '-');
n = 0;
while (p < lim)
{
++n;
p += prline(p, ':');
}
bufpending = out_after;
used = 1;
return n;
}
/* Fill the user-visible portion of the buffer, returning a byte count. */
static int
fillbuf()
{
register char *b, *d, *l;
int i, cc;
size_t discard, save;
prpending(buflim);
b = buflim;
for (i = 0; i < out_before; ++i)
if (b > buflast)
do
--b;
while (b > buflast && b[-1] != '\n');
if (buflast < b)
bufgap = 1;
if (out_line)
nlscan(b);
discard = b - buf;
save = buflim - b;
if (b > buf)
{
d = buf;
l = buf + bufcc;
while (b < l)
*d++ = *b++;
}
bufcc -= discard;
buftotalcc += discard;
do
{
if (!bufeof)
{
if (bufcc > bufalloc / 2)
buf = xrealloc(buf, bufalloc *= 2);
cc = read(bufdesc, buf + bufcc, bufalloc - bufcc);
if (cc < 0)
{
error(buffile, errno);
bufeof = 1;
}
else
{
bufeof = !cc;
bufcc += cc;
}
}
bufbeg = buf + save;
for (l = buf + bufcc; l > bufbeg && l[-1] != '\n'; --l)
;
buflim = l;
buflast = buf;
}
while (!bufeof && bufbeg == buflim);
if (bufeof)
buflim = buf + bufcc;
return buflim - bufbeg;
}
/* One-time initialization. */
static void
initbuf()
{
bufalloc = 8192;
buf = xmalloc(bufalloc);
}
/* Reset the buffer for a new file. */
static void
DEFUN(resetbuf, (desc, file), int desc AND const char *file)
{
bufbeg = buf;
buflim = buf;
bufcc = 0;
buftotalcc = 0;
buflast = buf;
bufgap = 0;
buftotalnl = 0;
bufpending = 0;
bufdesc = desc;
bufeof = 0;
buffile = file;
}
/* Scan the user-visible portion of the buffer, calling prtext() for
matching lines (or between matching lines if OUT_INVERT is true).
Return a count of lines printed. */
static int
grepbuf()
{
int total;
register char *p, *b, *l;
total = 0;
p = bufbeg;
while (b = execute(p, buflim - p))
{
if (b == buflim && (b > bufbeg && b[-1] == '\n' || b == bufbeg))
break;
while (b > bufbeg && b[-1] != '\n')
--b;
l = b + 1;
while (l < buflim && l[-1] != '\n')
++l;
if (!out_invert)
total += prtext(b, l);
else if (p < b)
total += prtext(p, b);
p = l;
}
if (out_invert && p < buflim)
total += prtext(p, buflim);
return total;
}
/* Scan the given file, returning a count of lines printed. */
static int
DEFUN(grep, (desc, file), int desc AND const char *file)
{
int total;
total = 0;
resetbuf(desc, file);
while (fillbuf())
total += grepbuf();
return total;
}
static const char version[] = "GNU fgrep, version 1.1";
#define USAGE \
"usage: %s [-[[AB] ]<num>] [-[CVchilnsvwx]] [-[ef]] <expr> [<files...>]\n"
static void
usage()
{
fprintf(stderr, USAGE, prog);
exit(2);
}
int
DEFUN(main, (argc, argv), int argc AND char *argv[])
{
char *keys;
size_t keycc, keyalloc;
int count_matches, no_filenames, list_files;
int opt, cc, desc, count, status;
FILE *fp;
prog = argv[0];
if (prog && strrchr(prog, '/'))
prog = strrchr(prog, '/') + 1;
keys = NULL;
count_matches = 0;
no_filenames = 0;
list_files = 0;
while ((opt = getopt(argc, argv, "0123456789A:B:CVbce:f:hilnsvwxy")) != EOF)
switch (opt)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
out_before = 10 * out_before + opt - '0';
out_after = 10 * out_after + opt - '0';
break;
case 'A':
out_after = atoi(optarg);
if (out_after < 0)
usage();
break;
case 'B':
out_before = atoi(optarg);
if (out_before < 0)
usage();
break;
case 'C':
out_before = out_after = 2;
break;
case 'V':
fprintf(stderr, "%s\n", version);
break;
case 'b':
out_byte = 1;
break;
case 'c':
out_silent = 1;
count_matches = 1;
break;
case 'e':
if (keys)
usage();
keys = optarg;
keycc = strlen(keys);
break;
case 'f':
if (keys)
usage();
fp = strcmp(optarg, "-") ? fopen(optarg, "r") : stdin;
if (!fp)
fatal(optarg, errno);
keyalloc = 1024;
keys = xmalloc(keyalloc);
keycc = 0;
while (!feof(fp)
&& (cc = fread(keys + keycc, 1, keyalloc - keycc, fp)) > 0)
{
keycc += cc;
if (keycc == keyalloc)
keys = xrealloc(keys, keyalloc *= 2);
}
if (fp != stdin)
fclose(fp);
break;
case 'h':
no_filenames = 1;
break;
case 'i':
case 'y': /* For old-timers . . . */
match_fold = 1;
break;
case 'l':
out_silent = 1;
list_files = 1;
break;
case 'n':
out_line = 1;
break;
case 's':
out_silent = 1;
break;
case 'v':
out_invert = 1;
break;
case 'w':
match_words = 1;
break;
case 'x':
match_lines = 1;
break;
default:
usage();
break;
}
if (!keys)
if (optind < argc)
{
keys = argv[optind++];
keycc = strlen(keys);
}
else
usage();
compile(keys, keycc);
if (argc - optind > 1 && !no_filenames)
out_file = 1;
status = 1;
initbuf();
if (optind < argc)
while (optind < argc)
{
desc = strcmp(argv[optind], "-") ? open(argv[optind], 0) : 0;
if (desc < 0)
error(argv[optind], errno);
else
{
count = grep(desc, argv[optind]);
if (count_matches)
{
if (out_file)
printf("%s:", argv[optind]);
printf("%d\n", count);
}
if (count)
{
status = 0;
if (list_files)
printf("%s\n", argv[optind]);
}
}
if (desc)
close(desc);
++optind;
}
else
{
count = grep(0, "<stdin>");
if (count_matches)
printf("%d\n", count);
if (count)
{
status = 0;
if (list_files)
printf("%s\n", argv[optind]);
}
}
exit(error_seen ? 2 : status);
}