/*
* grep.c
* Implementation of POSIX 2008 grep utility
* Copyright © 2012 Rich Felker
* Licensed under the terms of the GNU General Public License, v2 or later
*/
#include <stdio.h>
#include <regex.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
struct pattern {
struct pattern *next;
char *str;
regex_t re;
};
static struct pattern *head, *tail;
static void usage()
{
exit(1);
}
static void addpats(const char *prog, int mode, const char *src)
{
FILE *f;
char *line = 0;
ssize_t llen = 0;
struct pattern *pat;
f = (mode=='f') ? fopen(src, "rb")
: fmemopen((void *)src, strlen(src), "rb");
if (!f) {
fprintf(stderr, "%s: ", prog);
perror(mode=='f' ? src : 0);
exit(1);
}
while ((llen = getline(&line, (size_t[]){0}, f)) >= 0) {
if (llen && line[llen-1]=='\n')
line[--llen] = 0;
pat = malloc(sizeof *pat);
if (!pat) {
perror(prog);
exit(1);
}
pat->str = line;
pat->next = 0;
if (tail) tail->next = pat;
else head = tail = pat;
line = 0;
llen = 0;
}
fclose(f);
}
#define FLAG_E (1U<<0)
#define FLAG_F (1U<<1)
#define FLAG_c (1U<<2)
#define FLAG_i (1U<<7)
#define FLAG_l (1U<<8)
#define FLAG_n (1U<<9)
#define FLAG_q (1U<<10)
#define FLAG_s (1U<<11)
#define FLAG_v (1U<<12)
#define FLAG_x (1U<<13)
int main(int argc, char **argv)
{
static const char *optpat = "EFce:f:ilnqsvx";
FILE *f;
char *line = 0;
size_t lsize = 0;
ssize_t llen;
int b;
int multifile;
struct pattern *pat;
int match, any_matches = 0;
long long count, lineno;
unsigned flags = 0;
const char *filename;
int err;
while ((b=getopt(argc, argv, optpat))!=EOF) {
flags |= 1U << strchr(optpat, b)-optpat;
if (b-'e'<2U) addpats(argv[0], b, optarg);
}
if (!head) {
if (!argv[optind]) usage();
addpats(argv[0], 'e', argv[optind++]);
}
if (!(flags & FLAG_F)) {
int re_opts = (flags & FLAG_E)
? REG_NOSUB|REG_EXTENDED
: REG_NOSUB;
if (flags & FLAG_i) re_opts |= REG_ICASE;
for (pat=head; pat; pat=pat->next) {
if (flags & FLAG_x) {
char *tmp = malloc(strlen(pat->str)+3);
if (!tmp) {
perror(argv[0]);
exit(1);
}
sprintf(tmp, "%s%s%s",
pat->str[0]=='^'?"":"^", pat->str,
pat->str[strlen(pat->str)-1]=='$'?"":"$");
free(pat->str);
pat->str = tmp;
}
if ((err=regcomp(&pat->re, pat->str, re_opts))) {
char errstr[256];
regerror(err, &pat->re, errstr, sizeof errstr);
fprintf(stderr, "%s: %s\n", argv[0], errstr);
exit(1);
}
free(pat->str);
}
}
if (!argv[optind]) {
optind--;
filename = "(standard input)";
f = stdin;
multifile = 0;
goto process_stdin;
}
multifile = !!argv[optind+1];
for (; argv[optind]; optind++) {
filename = argv[optind];
f = fopen(filename, "rb");
if (!f) {
if (!(flags & FLAG_s)) {
fprintf(stderr, "%s: ", argv[0]);
perror(filename);
}
continue;
}
process_stdin:
count = 0;
lineno = 0;
while ((llen = getline(&line, &lsize, f)) >= 0) {
lineno++;
if (llen && line[llen-1]=='\n')
line[--llen] = 0;
match = 0;
for (pat=head; pat; pat=pat->next) {
if (flags & FLAG_F) {
if (flags & FLAG_x) {
if (!strcmp(line, pat->str)) {
match = 1;
break;
}
} else if (strstr(line, pat->str)) {
match = 1;
break;
}
} else if (!regexec(&pat->re, line, 0, 0, 0)) {
match = 1;
break;
}
}
if (flags & FLAG_v) match = !match;
if (match) {
any_matches = 1;
if (flags & FLAG_q) exit(0);
if (flags & FLAG_l) {
puts(filename);
break;
}
if (flags & FLAG_c) {
count++;
continue;
}
if (multifile) {
if (flags & FLAG_n)
printf("%s:%lld:%s\n", filename, lineno, line);
else
printf("%s:%s\n", filename, line);
} else {
if (flags & FLAG_n)
printf("%lld:%s\n", lineno, line);
else
puts(line);
}
}
}
if (ferror(f) && !(flags & FLAG_s)) {
fprintf(stderr, "%s: ", argv[0]);
perror(filename);
}
if (flags & FLAG_c) {
if (multifile)
printf("%s:%lld\n", filename, count);
else
printf("%lld\n", count);
}
fclose(f);
}
return !any_matches;
}