355 lines
8.9 KiB
C
Executable file
355 lines
8.9 KiB
C
Executable file
/* fgrep - fast grep Author: Bert Gijsbers */
|
|
|
|
/* Copyright (c) 1991 by Bert Gijsbers. All rights reserved.
|
|
* Permission to use and redistribute this software is hereby granted provided
|
|
* that this copyright notice remains intact and that any modifications are
|
|
* clearly marked as such.
|
|
*
|
|
* syntax:
|
|
* fgrep -chlnsv <[-e string] ... [-f file] ... | string> [file] ...
|
|
* options:
|
|
* -c : print the number of matching lines
|
|
* -h : don't print file name headers if more than one file
|
|
* -l : print only the file names of the files containing a match
|
|
* -n : print line numbers
|
|
* -s : don't print, return status only
|
|
* -v : reverse, lines not containing one of the strings match
|
|
* -e string : search for this string
|
|
* -f file : file contains strings to search for
|
|
* notes:
|
|
* Options are processed by getopt(3).
|
|
* Multiple strings per command line are supported, eg.
|
|
* fgrep -e str1 -e str2 *.c
|
|
* Instead of a filename - is allowed, meaning standard input.
|
|
*/
|
|
|
|
/* #include <ansi.h> */
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#define MAX_STR_LEN 256 /* maximum length of strings to search for */
|
|
#define BYTE 0xFF /* convert from char to int */
|
|
#define READ_SIZE 4096 /* read() request size */
|
|
#define BUF_SIZE (2*READ_SIZE) /* size of buffer */
|
|
|
|
typedef struct test_str {
|
|
struct test_str *next; /* linked list */
|
|
char *str; /* string to be found */
|
|
char *str_end; /* points to last character */
|
|
int len; /* string length */
|
|
char *bufp; /* pointer into input buffer */
|
|
unsigned char table[256]; /* table for Boyer-Moore algorithm */
|
|
} test_str;
|
|
|
|
test_str *strings;
|
|
char *prog_name;
|
|
int cflag, hflag, lflag, nflag, sflag, vflag;
|
|
unsigned line_num; /* line number in current file */
|
|
|
|
int fd_in, eof_seen; /* file descriptor for input and eof status */
|
|
char input_buffer[BUF_SIZE + 2];/* buffer + sentinel margin */
|
|
#define buffer (&input_buffer[2])
|
|
|
|
/* Pointers into the input buffer */
|
|
char *input; /* points to current input char */
|
|
char *max_input; /* points to first invalid char */
|
|
char *buf_end; /* points to first char not read */
|
|
|
|
/* Error messages */
|
|
char no_mem[] = "not enough memory";
|
|
char no_arg[] = "argument missing";
|
|
|
|
extern char *optarg;
|
|
extern int optind;
|
|
|
|
_PROTOTYPE(int main, (int argc, char **argv));
|
|
_PROTOTYPE(char *search_str, (test_str * ts));
|
|
_PROTOTYPE(int fill_buffer, (void));
|
|
_PROTOTYPE(void failure, (char *mesg));
|
|
_PROTOTYPE(void file_open, (void));
|
|
_PROTOTYPE(void usage, (void));
|
|
_PROTOTYPE(char *get_line, (void));
|
|
_PROTOTYPE(void string_file, (void));
|
|
_PROTOTYPE(void add_string, (char *str));
|
|
_PROTOTYPE(int getopt, (int argc, char **argv, char *optstring));
|
|
|
|
int main(argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
char *line;
|
|
int c;
|
|
unsigned count; /* number of matching lines in current file */
|
|
unsigned found_one = 0; /* was there any match in any file at all ? */
|
|
|
|
#ifdef noperprintf
|
|
noperprintf(stdout);
|
|
#else
|
|
static char outbuf[BUFSIZ];
|
|
|
|
setvbuf(stdout, outbuf, _IOFBF, sizeof outbuf);
|
|
#endif
|
|
|
|
prog_name = argv[0];
|
|
if (argc == 1) usage();
|
|
while ((c = getopt(argc, argv, "ce:f:hlnsv")) != EOF) {
|
|
switch (c) {
|
|
case 'c': cflag++; break;
|
|
case 'e': add_string(optarg); break;
|
|
case 'f': string_file(); break;
|
|
case 'h': hflag++; break;
|
|
case 'l': lflag++; break;
|
|
case 'n': nflag++; break;
|
|
case 's': sflag++; break;
|
|
case 'v': vflag++; break;
|
|
default: usage(); break;
|
|
}
|
|
}
|
|
|
|
/* If no -e or -f option is used take a string from the command line. */
|
|
if (strings == (test_str *) NULL) {
|
|
if (optind == argc) failure(no_arg);
|
|
add_string(argv[optind++]);
|
|
}
|
|
if (argc - optind < 2)
|
|
hflag++; /* don't print filenames if less than two
|
|
* files */
|
|
|
|
/* Handle every matching line according to the flags. */
|
|
do {
|
|
optarg = argv[optind];
|
|
file_open();
|
|
count = 0;
|
|
while ((line = get_line()) != (char *) NULL) {
|
|
count++;
|
|
if (sflag) return 0;
|
|
if (lflag) {
|
|
printf("%s\n", optarg);
|
|
fflush(stdout);
|
|
break;
|
|
}
|
|
if (cflag) continue;
|
|
if (hflag == 0) printf("%s:", optarg);
|
|
if (nflag) printf("%u:", line_num);
|
|
do {
|
|
putchar(*line);
|
|
} while (++line < input);
|
|
fflush(stdout);
|
|
}
|
|
found_one |= count;
|
|
if (cflag) {
|
|
if (hflag == 0) printf("%s: ", optarg);
|
|
printf("%u\n", count);
|
|
fflush(stdout);
|
|
}
|
|
close(fd_in);
|
|
} while (++optind < argc);
|
|
|
|
/* Exit nonzero if no match is found. */
|
|
return found_one ? 0 : 1;
|
|
}
|
|
|
|
void usage()
|
|
{
|
|
fprintf(stderr,
|
|
"Usage: %s -chlnsv <[-e string] ... [-f file] ... | string> [file] ...\n",
|
|
prog_name);
|
|
exit(2);
|
|
}
|
|
|
|
void failure(mesg)
|
|
char *mesg;
|
|
{
|
|
fprintf(stderr, "%s: %s\n", prog_name, mesg);
|
|
exit(1);
|
|
}
|
|
|
|
/* Add a string to search for to the global linked list `strings'. */
|
|
void add_string(str)
|
|
char *str;
|
|
{
|
|
test_str *ts;
|
|
int len;
|
|
|
|
if (str == (char *) NULL || (len = strlen(str)) == 0) return;
|
|
if (len > MAX_STR_LEN) failure("string too long");
|
|
if ((ts = (test_str *) malloc(sizeof(*ts))) == (test_str *) NULL)
|
|
failure(no_mem);
|
|
|
|
/* Initialize Boyer-Moore table. */
|
|
memset(ts->table, len, sizeof(ts->table));
|
|
ts->len = len;
|
|
ts->str = str;
|
|
ts->str_end = str + len - 1;
|
|
for (; --len >= 0; str++) ts->table[*str & BYTE] = len;
|
|
|
|
/* Put it on the list */
|
|
ts->next = strings;
|
|
strings = ts;
|
|
}
|
|
|
|
/* Open a file for reading. Initialize input buffer pointers. */
|
|
void file_open()
|
|
{
|
|
/* Use stdin if no file arguments are given on the command line. */
|
|
if (optarg == (char *) NULL || strcmp(optarg, "-") == 0) {
|
|
fd_in = 0;
|
|
optarg = "stdin";
|
|
} else if ((fd_in = open(optarg, O_RDONLY)) == -1) {
|
|
fprintf(stderr, "%s: can't open %s\n", prog_name, optarg);
|
|
exit(1);
|
|
}
|
|
input = max_input = buf_end = buffer;
|
|
buffer[-1] = '\n'; /* sentinel */
|
|
eof_seen = 0;
|
|
line_num = 0;
|
|
}
|
|
|
|
/* Move any leftover characters to the head of the buffer.
|
|
* Read characters into the rest of the buffer.
|
|
* Round off the available input to whole lines.
|
|
* Return the number of valid input characters.
|
|
*/
|
|
int fill_buffer()
|
|
{
|
|
char *bufp;
|
|
int size;
|
|
|
|
if (eof_seen) return 0;
|
|
|
|
size = buf_end - max_input;
|
|
memmove(buffer, max_input, size);
|
|
bufp = &buffer[size];
|
|
|
|
do {
|
|
if ((size = read(fd_in, bufp, READ_SIZE)) <= 0) {
|
|
if (size != 0) failure("read error");
|
|
eof_seen++;
|
|
if (bufp == buffer) /* no input left */
|
|
return 0;
|
|
/* Make sure the last char of a file is '\n'. */
|
|
*bufp++ = '\n';
|
|
break;
|
|
}
|
|
bufp += size;
|
|
} while (bufp - buffer < READ_SIZE && bufp[-1] != '\n');
|
|
|
|
buf_end = bufp;
|
|
while (*--bufp != '\n');
|
|
if (++bufp == buffer) {
|
|
/* Line too long. */
|
|
*buf_end++ = '\n';
|
|
bufp = buf_end;
|
|
}
|
|
max_input = bufp;
|
|
input = buffer;
|
|
|
|
return max_input - buffer;
|
|
}
|
|
|
|
/* Read strings from a file. Give duplicates to add_string(). */
|
|
void string_file()
|
|
{
|
|
char *str, *p;
|
|
|
|
file_open();
|
|
while (input < max_input || fill_buffer() > 0) {
|
|
p = (char *) memchr(input, '\n', BUF_SIZE);
|
|
*p++ = '\0';
|
|
if ((str = (char *) malloc(p - input)) == (char *) NULL)
|
|
failure(no_mem);
|
|
memcpy(str, input, p - input);
|
|
add_string(str);
|
|
input = p;
|
|
}
|
|
close(fd_in);
|
|
}
|
|
|
|
/* Scan the rest of the available input for a string using Boyer-Moore.
|
|
* Return a pointer to the match or a pointer beyond end of input if no match.
|
|
* Record how far the input is scanned.
|
|
*/
|
|
char *search_str(ts)
|
|
test_str *ts;
|
|
{
|
|
char *bufp, *prevbufp, *s;
|
|
|
|
bufp = input + ts->len - 1;
|
|
while (bufp < max_input) {
|
|
prevbufp = bufp;
|
|
bufp += ts->table[*bufp & BYTE];
|
|
if (bufp > prevbufp) continue;
|
|
s = ts->str_end;
|
|
do {
|
|
if (s == ts->str) { /* match found */
|
|
ts->bufp = bufp;
|
|
return bufp;
|
|
}
|
|
} while (*--bufp == *--s);
|
|
bufp = prevbufp + 1;
|
|
}
|
|
ts->bufp = bufp;
|
|
|
|
return bufp;
|
|
}
|
|
|
|
/* Return the next line in which one of the strings occurs.
|
|
* Or, if the -v option is used, the next line without a match.
|
|
* Or NULL on EOF.
|
|
*/
|
|
char *get_line()
|
|
{
|
|
test_str *ts;
|
|
char *match, *line;
|
|
|
|
/* Loop until a line is found. */
|
|
while (1) {
|
|
if (input >= max_input && fill_buffer() == 0) { /* EOF */
|
|
line = (char *) NULL;
|
|
break;
|
|
}
|
|
|
|
/* If match is still equal to max_input after the next loop
|
|
* then no match is found. */
|
|
match = max_input;
|
|
ts = strings;
|
|
do {
|
|
if (input == buffer) {
|
|
if (search_str(ts) < match) match = ts->bufp;
|
|
} else if (ts->bufp < match) {
|
|
if (ts->bufp >= input || search_str(ts) < match)
|
|
match = ts->bufp;
|
|
}
|
|
} while ((ts = ts->next) != (test_str *) NULL);
|
|
|
|
/* Determine if and in what line a match is found. Only do
|
|
* line number counting if it is necessary or very easy. */
|
|
if (vflag) {
|
|
line_num++;
|
|
line = input;
|
|
input = 1 + (char *) memchr(line, '\n', BUF_SIZE);
|
|
if (input <= match) break; /* no match in current line */
|
|
} else if (nflag) {
|
|
do {
|
|
line_num++;
|
|
line = input;
|
|
input = 1 + (char *) memchr(line, '\n', BUF_SIZE);
|
|
} while (input < match ||
|
|
(input == match && match < max_input));
|
|
if (match < max_input) break; /* match found */
|
|
} else if (match < max_input) {
|
|
/* Match found. */
|
|
for (line = match; *--line != '\n';);
|
|
line++;
|
|
input = 1 + (char *) memchr(match, '\n', BUF_SIZE);
|
|
break;
|
|
} else
|
|
input = max_input;
|
|
}
|
|
|
|
return line;
|
|
}
|