#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <limits.h>
#include <unistd.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#include "../local_includes/regex.h"
#ifdef HAVE_GETTEXT
#include <libintl.h>
#else
#define gettext(s) s
#define bindtextdomain(p, d)
#define textdomain(p)
#endif
#define _(String) gettext(String)
#undef MAX
#undef MIN
#define MAX(a, b) (((a) >= (b)) ? (a) : (b))
#define MIN(a, b) (((a) <= (b)) ? (a) : (b))
static char const short_options[] =
"cd:e:hiklm:nqsvwyBD:E:HI:MS:V0123456789-:";
static int show_help;
char *program_name;
#ifdef HAVE_GETOPT_LONG
enum {
COLOR_OPTION = CHAR_MAX + 1,
SHOW_POSITION_OPTION
};
static struct option const long_options[] =
{
{"best-match", no_argument, NULL, 'B'},
{"color", no_argument, NULL, COLOR_OPTION},
{"colour", no_argument, NULL, COLOR_OPTION},
{"count", no_argument, NULL, 'c'},
{"delete-cost", required_argument, NULL, 'D'},
{"delimiter", required_argument, NULL, 'd'},
{"delimiter-after", no_argument, NULL, 'M'},
{"files-with-matches", no_argument, NULL, 'l'},
{"help", no_argument, &show_help, 1},
{"ignore-case", no_argument, NULL, 'i'},
{"insert-cost", required_argument, NULL, 'I'},
{"invert-match", no_argument, NULL, 'v'},
{"line-number", no_argument, NULL, 'n'},
{"max-count", required_argument, NULL, 'm'},
{"literal", no_argument, NULL, 'k'},
{"max-errors", required_argument, NULL, 'E'},
{"no-filename", no_argument, NULL, 'h'},
{"nothing", no_argument, NULL, 'y'},
{"quiet", no_argument, NULL, 'q'},
{"record-number", no_argument, NULL, 'n'},
{"regexp", required_argument, NULL, 'e'},
{"show-cost", no_argument, NULL, 's'},
{"show-position", no_argument, NULL, SHOW_POSITION_OPTION},
{"silent", no_argument, NULL, 'q'},
{"substitute-cost", required_argument, NULL, 'S'},
{"version", no_argument, NULL, 'V'},
{"with-filename", no_argument, NULL, 'H'},
{"word-regexp", no_argument, NULL, 'w'},
{0, 0, 0, 0}
};
#endif
static void
tre_agrep_usage(int status)
{
if (status != 0)
{
fprintf(stderr, _("Usage: %s [OPTION]... PATTERN [FILE]...\n"),
program_name);
fprintf(stderr, _("Try `%s --help' for more information.\n"),
program_name);
}
else
{
printf(_("Usage: %s [OPTION]... PATTERN [FILE]...\n"), program_name);
printf(_("\
Searches for approximate matches of PATTERN in each FILE or standard input.\n\
Example: `%s -2 optimize foo.txt' outputs all lines in file `foo.txt' that\n\
match \"optimize\" within two errors. E.g. lines which contain \"optimise\",\n\
\"optmise\", and \"opitmize\" all match.\n"), program_name);
printf("\n");
printf(_("\
Regexp selection and interpretation:\n\
-e, --regexp=PATTERN use PATTERN as a regular expression\n\
-i, --ignore-case ignore case distinctions\n\
-k, --literal PATTERN is a literal string\n\
-w, --word-regexp force PATTERN to match only whole words\n\
\n\
Approximate matching settings:\n\
-D, --delete-cost=NUM set cost of missing characters\n\
-I, --insert-cost=NUM set cost of extra characters\n\
-S, --substitute-cost=NUM set cost of wrong characters\n\
-E, --max-errors=NUM select records that have at most NUM errors\n\
-# select records that have at most # errors (# is a\n\
digit between 0 and 9)\n\
\n\
Miscellaneous:\n\
-d, --delimiter=PATTERN set the record delimiter regular expression\n\
-v, --invert-match select non-matching records\n\
-V, --version print version information and exit\n\
-y, --nothing does nothing (for compatibility with the non-free\n\
agrep program)\n\
--help display this help and exit\n\
\n\
Output control:\n\
-B, --best-match only output records with least errors\n\
-c, --count only print a count of matching records per FILE\n\
-h, --no-filename suppress the prefixing filename on output\n\
-H, --with-filename print the filename for each match\n\
-l, --files-with-matches only print FILE names containing matches\n\
-M, --delimiter-after print record delimiter after record if -d is used\n\
-m, --max-count=NUM stop after NUM selected lines\n\
-n, --record-number print record number with output\n\
--line-number same as -n\n\
-q, --quiet, --silent suppress all normal output\n\
-s, --show-cost print match cost with output\n\
--colour, --color use markers to distinguish the matching \
strings\n\
--show-position prefix each output record with start and end\n\
position of the first match within the record\n"));
printf("\n");
printf(_("\
With no FILE, or when FILE is -, reads standard input. If less than two\n\
FILEs are given, -h is assumed. Exit status is 0 if a match is found, 1 for\n\
no match, and 2 if there were errors. If -E or -# is not specified, only\n\
exact matches are selected.\n"));
printf("\n");
printf(_("\
PATTERN is a POSIX extended regular expression (ERE) with the TRE extensions.\n\
See tre(7) for a complete description.\n"));
printf("\n");
printf(_("Report bugs to: "));
printf("%s.\n", PACKAGE_BUGREPORT);
}
exit(status);
}
static regex_t preg;
static regex_t delim;
#define INITIAL_BUF_SIZE 10240
static char *buf;
static int buf_size;
static int data_len;
static char *record;
static char *next_record;
static int record_len;
static int delim_len;
static int next_delim_len;
static int delim_after = 1;
static int at_eof;
static int have_matches;
static int invert_match;
static int print_filename;
static int print_recnum;
static int print_cost;
static int count_matches;
static int max_count;
static int list_files;
static int color_option;
static int print_position;
static int best_match;
static int best_cost;
static int be_silent;
static regaparams_t match_params;
static const char *highlight = "01;31";
static inline int
tre_agrep_get_next_record(int fd, const char *filename)
{
if (at_eof)
return 1;
while (1)
{
int errcode;
regmatch_t pmatch[1];
if (next_record == NULL)
{
int r;
int read_size = buf_size - data_len;
if (read_size <= 0)
{
buf_size *= 2;
buf = realloc(buf, buf_size);
if (buf == NULL)
{
fprintf(stderr, "%s: %s\n", program_name, _("Out of memory"));
exit(2);
}
read_size = buf_size - data_len;
}
r = read(fd, buf + data_len, read_size);
if (r < 0)
{
char *err;
if (errno == EINTR)
continue;
err = strerror(errno);
fprintf(stderr, "%s: ", program_name);
fprintf(stderr, _("Error reading from %s: %s\n"), filename, err);
return 1;
}
if (r == 0)
{
record = buf;
record_len = data_len;
at_eof = 1;
if (record_len == 0)
return 1;
return 0;
}
data_len += r;
next_record = buf;
}
errcode = tre_regnexec(&delim, next_record, data_len - (next_record - buf),
1, pmatch, 0);
switch (errcode)
{
case REG_OK:
record = next_record;
record_len = pmatch[0].rm_so;
delim_len = next_delim_len;
next_delim_len = pmatch[0].rm_eo - pmatch[0].rm_so;
next_record = next_record + pmatch[0].rm_eo;
return 0;
break;
case REG_NOMATCH:
if (next_record == buf)
{
next_record = NULL;
continue;
}
memmove(buf, next_record, buf + data_len - next_record);
data_len = buf + data_len - next_record;
next_record = NULL;
continue;
break;
case REG_ESPACE:
fprintf(stderr, "%s: %s\n", program_name, _("Out of memory"));
exit(2);
break;
default:
assert(0);
break;
}
}
}
static int
tre_agrep_handle_file(const char *filename)
{
int fd;
int count = 0;
int recnum = 0;
if (buf == NULL)
{
buf = malloc(INITIAL_BUF_SIZE);
if (buf == NULL)
{
fprintf(stderr, "%s: %s\n", program_name, _("Out of memory"));
exit(2);
}
buf_size = INITIAL_BUF_SIZE;
}
next_record = NULL;
data_len = 0;
if (!filename || strcmp(filename, "-") == 0)
{
if (best_match)
{
fprintf(stderr, "%s: %s\n", program_name,
_("Cannot use -B when reading from standard input."));
return 2;
}
fd = 0;
filename = _("(standard input)");
}
else
{
fd = open(filename, O_RDONLY);
}
if (fd < 0)
{
fprintf(stderr, "%s: %s: %s\n", program_name, filename, strerror(errno));
return 1;
}
at_eof = 0;
while (!tre_agrep_get_next_record(fd, filename))
{
int errcode;
regamatch_t match;
regmatch_t pmatch[1];
recnum++;
memset(&match, 0, sizeof(match));
if (best_match)
match_params.max_cost = best_cost;
if (color_option || print_position)
{
match.pmatch = pmatch;
match.nmatch = 1;
}
if (best_match == 1 && best_cost == 0)
break;
errcode = tre_reganexec(&preg, record, record_len, &match, match_params, 0);
if ((!invert_match && errcode == REG_OK)
|| (invert_match && errcode == REG_NOMATCH))
{
if (be_silent)
exit(0);
count++;
have_matches = 1;
if (best_match)
{
if (best_match == 1)
{
if (match.cost < best_cost)
best_cost = match.cost;
continue;
}
if (match.cost > best_cost)
continue;
}
if (list_files)
{
printf("%s\n", filename);
break;
}
else if (!count_matches)
{
if (print_filename)
printf("%s:", filename);
if (print_recnum)
printf("%d:", recnum);
if (print_cost)
printf("%d:", match.cost);
if (print_position)
printf("%d-%d:",
invert_match ? 0 : (int)pmatch[0].rm_so,
invert_match ? record_len : (int)pmatch[0].rm_eo);
if (delim_after)
{
record_len += next_delim_len;
}
else
{
record -= delim_len;
record_len += delim_len;
pmatch[0].rm_so += delim_len;
pmatch[0].rm_eo += delim_len;
}
if (color_option && !invert_match)
{
printf("%.*s", (int)pmatch[0].rm_so, record);
printf("\33[%sm", highlight);
printf("%.*s", (int)(pmatch[0].rm_eo - pmatch[0].rm_so),
record + pmatch[0].rm_so);
fputs("\33[00m", stdout);
printf("%.*s", (int)(record_len - pmatch[0].rm_eo),
record + pmatch[0].rm_eo);
}
else
{
printf("%.*s", record_len, record);
}
}
if (max_count > 0 && count >= max_count)
break;
}
}
if (count_matches && !best_match && !be_silent)
{
if (print_filename)
printf("%s:", filename);
printf("%d\n", count);
}
if (fd)
close(fd);
return 0;
}
int
main(int argc, char **argv)
{
int c, errcode;
int comp_flags = REG_EXTENDED;
char *tmp_str;
char *regexp = NULL;
const char *delim_regexp = "\n";
int word_regexp = 0;
int literal_string = 0;
int max_cost_set = 0;
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
program_name = argv[0];
if (program_name)
{
tmp_str = strrchr(program_name, '/');
if (tmp_str)
program_name = tmp_str + 1;
}
print_filename = -1;
print_cost = 0;
be_silent = 0;
tre_regaparams_default(&match_params);
match_params.max_cost = 0;
max_count = -1;
while (1)
{
#ifdef HAVE_GETOPT_LONG
c = getopt_long(argc, argv, short_options, long_options, NULL);
#else
c = getopt(argc, argv, short_options);
#endif
if (c == -1)
break;
switch (c)
{
case 'c':
count_matches = 1;
break;
case 'd':
delim_regexp = optarg;
if (delim_after == 1)
delim_after = 0;
break;
case 'e':
regexp = optarg;
break;
case 'h':
print_filename = 0;
break;
case 'i':
comp_flags |= REG_ICASE;
break;
case 'k':
literal_string = 1;
break;
case 'l':
list_files = 1;
break;
case 'm':
max_count = atoi(optarg);
break;
case 'n':
print_recnum = 1;
break;
case 'q':
be_silent = 1;
break;
case 's':
print_cost = 1;
break;
case 'v':
invert_match = 1;
break;
case 'w':
word_regexp = 1;
break;
case 'y':
break;
case 'B':
best_match = 1;
break;
case 'D':
match_params.cost_del = atoi(optarg);
break;
case 'E':
match_params.max_cost = atoi(optarg);
max_cost_set = 1;
break;
case 'H':
print_filename = 1;
break;
case 'I':
match_params.cost_ins = atoi(optarg);
break;
case 'M':
delim_after = 2;
break;
case 'S':
match_params.cost_subst = atoi(optarg);
break;
case 'V':
{
char *version;
tre_config(TRE_CONFIG_VERSION, &version);
printf("%s (TRE agrep) %s\n\n", program_name, version);
printf(_("\
Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>.\n"));
printf("\n");
exit(0);
break;
}
case '?':
break;
case '-':
if (strcmp(optarg, "color") == 0
|| strcmp(optarg, "colour") == 0)
color_option = 1;
else if (strcmp(optarg, "show-position") == 0)
print_position = 1;
else if (strcmp(optarg, "help") == 0)
show_help = 1;
else
{
fprintf(stderr, _("%s: invalid option --%s\n"),
program_name, optarg);
exit(2);
}
break;
#ifdef HAVE_GETOPT_LONG
case COLOR_OPTION:
color_option = 1;
break;
case SHOW_POSITION_OPTION:
print_position = 1;
break;
#endif
case 0:
break;
default:
if (c >= '0' && c <= '9')
match_params.max_cost = c - '0';
else
tre_agrep_usage(2);
max_cost_set = 1;
break;
}
}
if (show_help)
tre_agrep_usage(0);
if (max_count == 0)
return EXIT_FAILURE;
if (color_option)
{
char *user_highlight = getenv("GREP_COLOR");
if (user_highlight && *user_highlight != '\0')
highlight = user_highlight;
}
if (regexp == NULL)
{
if (optind >= argc)
tre_agrep_usage(2);
regexp = argv[optind++];
}
if (literal_string)
{
char *next_pos = regexp;
char *new_re, *new_re_end;
int n = 0;
int len;
next_pos = regexp;
while (next_pos)
{
next_pos = strstr(next_pos, "\\E");
if (next_pos)
{
n++;
next_pos += 2;
}
}
len = strlen(regexp);
new_re = malloc(len + 5 + n * 7);
if (!new_re)
{
fprintf(stderr, "%s: %s\n", program_name, _("Out of memory"));
return 2;
}
next_pos = regexp;
new_re_end = new_re;
strcpy(new_re_end, "\\Q");
new_re_end += 2;
while (next_pos)
{
char *start = next_pos;
next_pos = strstr(next_pos, "\\E");
if (next_pos)
{
strncpy(new_re_end, start, next_pos - start);
new_re_end += next_pos - start;
strcpy(new_re_end, "\\E\\\\E\\Q");
new_re_end += 7;
next_pos += 2;
}
else
{
strcpy(new_re_end, start);
new_re_end += strlen(start);
}
}
strcpy(new_re_end, "\\E");
regexp = new_re;
}
if (word_regexp)
{
char *tmp = regexp;
int len = strlen(tmp);
regexp = malloc(len + 7);
if (regexp == NULL)
{
fprintf(stderr, "%s: %s\n", program_name, _("Out of memory"));
return 2;
}
strcpy(regexp, "\\<(");
strcpy(regexp + 3, tmp);
strcpy(regexp + len + 3, ")\\>");
}
errcode = tre_regcomp(&preg, regexp, comp_flags);
if (errcode)
{
char errbuf[256];
tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
fprintf(stderr, "%s: %s: %s\n",
program_name, _("Error in search pattern"), errbuf);
return 2;
}
errcode = tre_regcomp(&delim, delim_regexp, REG_EXTENDED | REG_NEWLINE);
if (errcode)
{
char errbuf[256];
tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
fprintf(stderr, "%s: %s: %s\n",
program_name, _("Error in record delimiter pattern"), errbuf);
return 2;
}
if (tre_regexec(&delim, "", 0, NULL, 0) == REG_OK)
{
fprintf(stderr, "%s: %s\n", program_name,
_("Record delimiter pattern must not match an empty string"));
return 2;
}
if (print_filename == -1)
{
if (argc - optind <= 1)
print_filename = 0;
else
print_filename = 1;
}
if (optind >= argc)
{
tre_agrep_handle_file(NULL);
}
else if (best_match)
{
int first_ind = optind;
if (!max_cost_set)
match_params.max_cost = INT_MAX;
best_cost = INT_MAX;
while (optind < argc)
tre_agrep_handle_file(argv[optind++]);
if (best_cost == INT_MAX)
return 1;
match_params.max_cost = best_cost;
best_match = 2;
optind = first_ind;
while (optind < argc)
tre_agrep_handle_file(argv[optind++]);
}
else
{
while (optind < argc)
tre_agrep_handle_file(argv[optind++]);
}
return have_matches == 0;
}