|
|
@@ -0,0 +1,600 @@
|
|
|
+/* Copyright (c) 2008, Tim Post <tinkertim@gmail.com>
|
|
|
+ * All rights reserved.
|
|
|
+ *
|
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
|
+ *
|
|
|
+ * Redistributions of source code must retain the above copyright notice, this
|
|
|
+ * list of conditions and the following disclaimer.
|
|
|
+ *
|
|
|
+ * Redistributions in binary form must reproduce the above copyright notice,
|
|
|
+ * this list of conditions and the following disclaimer in the documentation
|
|
|
+ * and/or other materials provided with the distribution.
|
|
|
+ *
|
|
|
+ * Neither the name of the original program's authors nor the names of its
|
|
|
+ * contributors may be used to endorse or promote products derived from this
|
|
|
+ * software without specific prior written permission.
|
|
|
+ *
|
|
|
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
+ * POSSIBILITY OF SUCH DAMAGE.
|
|
|
+ */
|
|
|
+
|
|
|
+/* Some example usages:
|
|
|
+ * grawk shutdown '$5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15' messages
|
|
|
+ * grawk shutdown '$5, $6, $7, $8, $9, $10, " -- " $1, $2, $3' messages
|
|
|
+ * grawk dhclient '$1, $2 " \"$$\"-- " $3' syslog
|
|
|
+ * cat syslog | grawk dhclient '$0'
|
|
|
+ * cat myservice.log | grawk -F , error '$3'
|
|
|
+ *
|
|
|
+ * Contributors:
|
|
|
+ * Tim Post, Nicholas Clements, Alex Karlov
|
|
|
+ * We hope that you find this useful! */
|
|
|
+
|
|
|
+/* FIXME:
|
|
|
+ * readline() should probably be renamed
|
|
|
+ */
|
|
|
+
|
|
|
+/* TODO:
|
|
|
+ * Add a tail -f like behavior that applies expressions and fields
|
|
|
+ * Recursive (like grep -r) or at least honor symlinks ? */
|
|
|
+
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <string.h>
|
|
|
+#include <getopt.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <sys/stat.h>
|
|
|
+#include <regex.h>
|
|
|
+
|
|
|
+#define VERSION "1.0.7"
|
|
|
+#define MAINTAINER "Tim Post <echo@echoreply.us>"
|
|
|
+
|
|
|
+/* Storage structure to hold awk-style pattern */
|
|
|
+struct awk_pattern
|
|
|
+{
|
|
|
+ int maxfield; /* Maximum field number for $# fields */
|
|
|
+ int numfields; /* Number of awk pattern fields */
|
|
|
+ char **fields; /* The awk pattern fields */
|
|
|
+};
|
|
|
+
|
|
|
+typedef struct awk_pattern awk_pat_t;
|
|
|
+
|
|
|
+/* Option arguments */
|
|
|
+static struct option const long_options[] = {
|
|
|
+ { "ignore-case", no_argument, 0, 'i' },
|
|
|
+ { "with-filename", no_argument, 0, 'W' },
|
|
|
+ { "no-filename", no_argument, 0, 'w' },
|
|
|
+ { "line-number", no_argument, 0, 'n' },
|
|
|
+ { "field-separator", required_argument, 0, 'F' },
|
|
|
+ { "help", no_argument, 0, 'h' },
|
|
|
+ { "version", no_argument, 0, 'v' },
|
|
|
+ { 0, 0, 0, 0}
|
|
|
+};
|
|
|
+
|
|
|
+/* The official name of the program */
|
|
|
+const char *progname = "grawk";
|
|
|
+
|
|
|
+/* Global for delimiters used in tokenizing strings */
|
|
|
+char *tokdelim = NULL;
|
|
|
+
|
|
|
+/* Prototypes */
|
|
|
+static void usage(void);
|
|
|
+static int process(FILE *, regex_t, awk_pat_t, char *, int);
|
|
|
+static int process_line(char *, awk_pat_t, char *, char *);
|
|
|
+static int process_files(int, char **, regex_t, awk_pat_t, int, int);
|
|
|
+static int process_pipe(regex_t, awk_pat_t, int);
|
|
|
+static int awkcomp(awk_pat_t *, char *);
|
|
|
+static void awkfree(awk_pat_t *);
|
|
|
+static char *readline(FILE *);
|
|
|
+
|
|
|
+static void usage(void)
|
|
|
+{
|
|
|
+ printf("%s %s\n", progname, VERSION);
|
|
|
+ printf("Usage: %s [OPTION] PATTERN OUTPUT_PATTERN file1 [file2]...\n",
|
|
|
+ progname);
|
|
|
+ printf("Options:\n");
|
|
|
+ printf(" --help "
|
|
|
+ "show help and examples\n");
|
|
|
+ printf(" -i, --ignore-case "
|
|
|
+ "ignore case distinctions\n");
|
|
|
+ printf(" -W, --with-filename "
|
|
|
+ "Print filename for each match\n");
|
|
|
+ printf(" -w, --no-filename "
|
|
|
+ "Never print filename for each match\n");
|
|
|
+ printf(" -n, --line-number "
|
|
|
+ "Prefix each line of output with line number.\n");
|
|
|
+ printf(" -F fs, --field-separator=fs "
|
|
|
+ "Use fs as the field separator\n");
|
|
|
+ printf(" -h, --help "
|
|
|
+ "Print a brief help summary\n");
|
|
|
+ printf(" -v, --version "
|
|
|
+ "Print version information and exit normally\n");
|
|
|
+ printf(" PATTERN "
|
|
|
+ "a basic regular expression\n");
|
|
|
+ printf(" OUTPUT_PATTERN "
|
|
|
+ "awk-style print statement; defines "
|
|
|
+ "output fields\n");
|
|
|
+ printf("\nExamples:\n");
|
|
|
+ printf(" Retreive joe123's home directory from /etc/passwd:\n");
|
|
|
+ printf("\t%s -F : \"joe123\" '$6' /etc/passwd\n", progname);
|
|
|
+ printf("\n Find fields 2 3 and 4 on lines that begin with @ from stdin:\n");
|
|
|
+ printf("\tcat file.txt | %s \"^@\" '$2,$3,$4'\n", progname);
|
|
|
+ printf("\n Use as a simple grep:\n");
|
|
|
+ printf("\t%s \"string to find\" '$0' /file.txt\n", progname);
|
|
|
+ printf("\nReport bugs to %s\n", MAINTAINER);
|
|
|
+}
|
|
|
+
|
|
|
+/* readline() - read a line from the file handle.
|
|
|
+ * Return an allocated string */
|
|
|
+static char *readline(FILE *fp)
|
|
|
+{
|
|
|
+ char *str = (char *)NULL;
|
|
|
+ int ch = 0, len = 256, step = 256, i = 0;
|
|
|
+
|
|
|
+ str = (char *)malloc(len);
|
|
|
+ if (str == NULL)
|
|
|
+ return str;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ ch = fgetc(fp);
|
|
|
+ if (feof(fp))
|
|
|
+ break;
|
|
|
+ if (ch == '\n' || ch == '\r') {
|
|
|
+ str[i++] = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ str[i++] = ch;
|
|
|
+ if (i == len - 2) {
|
|
|
+ len += step;
|
|
|
+ str = (char *)realloc(str, len);
|
|
|
+ if (str == NULL) {
|
|
|
+ fclose(fp);
|
|
|
+ return str;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return str;
|
|
|
+}
|
|
|
+
|
|
|
+/* process() - this is the actual processing where we compare against a
|
|
|
+ * previously compiled grep pattern and output based on the awk pattern.
|
|
|
+ * The file is opened by the calling function. We pass in an empty string
|
|
|
+ * if we don't want to show the filename. If we want to show the line number,
|
|
|
+ * the value of show_lineno is 1. If we find a line, return 1. If no line is
|
|
|
+ * found, return 0. If an error occurs, return -1. */
|
|
|
+static int process(FILE *fp, regex_t re, awk_pat_t awk,
|
|
|
+ char *filename, int show_lineno)
|
|
|
+{
|
|
|
+ char *inbuf = NULL;
|
|
|
+ char slineno[32];
|
|
|
+ memset(slineno, 0, sizeof(slineno));
|
|
|
+ long lineno = 0;
|
|
|
+ int found = 0;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ inbuf = readline(fp);
|
|
|
+ if (!inbuf)
|
|
|
+ break;
|
|
|
+ if (feof(fp))
|
|
|
+ break;
|
|
|
+ lineno++;
|
|
|
+ if (regexec(&re, inbuf, (size_t)0, NULL, 0) == 0) {
|
|
|
+ found = 1; // Found a line.
|
|
|
+ if (show_lineno)
|
|
|
+ sprintf(slineno, "%ld:", lineno);
|
|
|
+ if (process_line(inbuf, awk, filename, slineno)) {
|
|
|
+ fprintf (stderr, "Error processing line [%s]\n", inbuf);
|
|
|
+ free (inbuf);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ free (inbuf);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (inbuf)
|
|
|
+ free(inbuf);
|
|
|
+
|
|
|
+ return found;
|
|
|
+}
|
|
|
+
|
|
|
+/* process_files() - process one or more files from the command-line.
|
|
|
+ * If at least one line is found, return 1, else return 0 if no lines
|
|
|
+ * were found or an error occurs. */
|
|
|
+static int process_files(int numfiles, char **files, regex_t re, awk_pat_t awk,
|
|
|
+ int show_filename, int show_lineno)
|
|
|
+{
|
|
|
+ int i, found = 0;
|
|
|
+ FILE *fp = NULL;
|
|
|
+ struct stat fstat;
|
|
|
+ char filename[1024];
|
|
|
+ memset(filename, 0, sizeof(filename));
|
|
|
+
|
|
|
+ for(i = 0; i < numfiles; i++) {
|
|
|
+ if (stat(files[i], &fstat) == -1) {
|
|
|
+ /* Did a file get deleted from the time we started running? */
|
|
|
+ fprintf (stderr,
|
|
|
+ "Error accessing file %s. No such file\n", files[i]);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (show_filename)
|
|
|
+ sprintf( filename, "%s:", files[i] );
|
|
|
+ /* For now, we aren't recursive. Perhaps allow symlinks? */
|
|
|
+ if ((fstat.st_mode & S_IFMT) != S_IFREG)
|
|
|
+ continue;
|
|
|
+ if (NULL == (fp = fopen(files[i], "r"))) {
|
|
|
+ fprintf(stderr,
|
|
|
+ "Error opening file %s. Permission denied\n", files[i]);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (process(fp, re, awk, filename, show_lineno) == 1)
|
|
|
+ found = 1;
|
|
|
+ fclose(fp);
|
|
|
+ }
|
|
|
+
|
|
|
+ return found;
|
|
|
+}
|
|
|
+
|
|
|
+/* process_pipe() - process input from stdin */
|
|
|
+static int process_pipe(regex_t re, awk_pat_t awk, int show_lineno)
|
|
|
+{
|
|
|
+ if (process(stdin, re, awk, "", show_lineno) == 1)
|
|
|
+ return 1;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* process_line() - process the line based on the awk-style pattern and output
|
|
|
+ * the results. */
|
|
|
+static int process_line(char *inbuf, awk_pat_t awk, char *filename, char *lineno)
|
|
|
+{
|
|
|
+ char full_line[3] = { '\1', '0', '\0' };
|
|
|
+
|
|
|
+ if (awk.numfields == 1 && strcmp(awk.fields[0], full_line) == 0) {
|
|
|
+ /* If the caller only wants the whole string, oblige, quickly. */
|
|
|
+ fprintf (stdout, "%s%s%s\n", filename, lineno, inbuf);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Build an array of fields from the line using strtok()
|
|
|
+ * TODO: make this re-entrant so that grawk can be spawned as a thread */
|
|
|
+ char **linefields = (char **)malloc((awk.maxfield + 1) * sizeof(char *));
|
|
|
+ char *wrkbuf = strdup(inbuf), *tbuf;
|
|
|
+
|
|
|
+ int count = 0, n = 1, i;
|
|
|
+ for (i = 0; i < (awk.maxfield + 1); i++) {
|
|
|
+ linefields[i] = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ tbuf = strtok(wrkbuf, tokdelim);
|
|
|
+ if(tbuf)
|
|
|
+ linefields[0] = strdup(tbuf);
|
|
|
+
|
|
|
+ while (tbuf != NULL) {
|
|
|
+ tbuf = strtok(NULL, tokdelim);
|
|
|
+ if (!tbuf)
|
|
|
+ break;
|
|
|
+ count++;
|
|
|
+ if (count > awk.maxfield)
|
|
|
+ break;
|
|
|
+ linefields[count] = strdup(tbuf);
|
|
|
+ if (!linefields[count]) {
|
|
|
+ fprintf(stderr, "Could not allocate memory to process file %s\n",
|
|
|
+ filename);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* For each field in the awk structure,
|
|
|
+ * find the field and print it to stdout.*/
|
|
|
+ fprintf(stdout, "%s%s", filename, lineno); /* if needed */
|
|
|
+ for (i = 0; i < awk.numfields; i++) {
|
|
|
+ if (awk.fields[i][0] == '\1') {
|
|
|
+ n = atoi(&awk.fields[i][1]);
|
|
|
+ if (n == 0) {
|
|
|
+ fprintf(stdout, "%s", inbuf);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (linefields[n-1])
|
|
|
+ fprintf(stdout, "%s", linefields[n-1]);
|
|
|
+ continue;
|
|
|
+ } else
|
|
|
+ fprintf(stdout, "%s", awk.fields[i]);
|
|
|
+ }
|
|
|
+ fprintf(stdout, "\n");
|
|
|
+ /* Cleanup */
|
|
|
+ if (wrkbuf)
|
|
|
+ free(wrkbuf);
|
|
|
+
|
|
|
+ for (i = 0; i < count; i++) {
|
|
|
+ free(linefields[i]);
|
|
|
+ linefields[i] = (char *) NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ free(linefields);
|
|
|
+ linefields = (char **)NULL;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* awkcomp() - little awk-style print format compilation routine.
|
|
|
+ * Returns structure with the apattern broken down into an array for easier
|
|
|
+ * comparison and printing. Handles string literals as well as fields and
|
|
|
+ * delimiters. Example: $1,$2 " \$ and \"blah\" " $4
|
|
|
+ * Returns -1 on error, else 0. */
|
|
|
+static int awkcomp(awk_pat_t *awk, char *apattern)
|
|
|
+{
|
|
|
+ awk->maxfield = 0;
|
|
|
+ awk->numfields = 0;
|
|
|
+ awk->fields = NULL;
|
|
|
+ awk->fields = (char **)malloc(sizeof(char *));
|
|
|
+
|
|
|
+ int i, num = 0;
|
|
|
+ char *wrkbuf;
|
|
|
+
|
|
|
+ wrkbuf = (char *)malloc(strlen(apattern) + 1);
|
|
|
+ if (wrkbuf == NULL) {
|
|
|
+ free(awk);
|
|
|
+ fprintf(stderr, "Memory allocation error (wrkbuf) in awkcomp()\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ int inString = 0, offs = 0;
|
|
|
+ char ch;
|
|
|
+ for (i = 0; i < strlen( apattern ); i++) {
|
|
|
+ ch = apattern[i];
|
|
|
+ if (inString && ch != '"' && ch != '\\') {
|
|
|
+ wrkbuf[offs++] = ch;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (ch == ' ')
|
|
|
+ continue;
|
|
|
+ switch (ch) {
|
|
|
+ /* Handle delimited strings inside of literal strings */
|
|
|
+ case '\\':
|
|
|
+ if (inString) {
|
|
|
+ wrkbuf[offs++] = apattern[++i];
|
|
|
+ continue;
|
|
|
+ } else {
|
|
|
+ /* Unexpected and unconventional escape (can get these
|
|
|
+ * from improper invocations of sed in a pipe with grawk),
|
|
|
+ * if sed is used to build the field delimiters */
|
|
|
+ fprintf(stderr,
|
|
|
+ "Unexpected character \'\\\' in output format\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ /* Beginning or ending of a literal string */
|
|
|
+ case '"':
|
|
|
+ inString = !inString;
|
|
|
+ if (inString)
|
|
|
+ continue;
|
|
|
+ break;
|
|
|
+ /* Handle the awk-like $# field variables */
|
|
|
+ case '$':
|
|
|
+ /* We use a non-printable ASCII character to
|
|
|
+ * delimit the string field values.*/
|
|
|
+ wrkbuf[offs++] = '\1';
|
|
|
+ /* We also need the max. field number */
|
|
|
+ num = 0;
|
|
|
+ while (1) {
|
|
|
+ ch = apattern[++i];
|
|
|
+ /* Not a number, exit this loop */
|
|
|
+ if (ch < 48 || ch > 57) {
|
|
|
+ i--;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ num = (num * 10) + (ch - 48);
|
|
|
+ wrkbuf[offs++] = ch;
|
|
|
+ }
|
|
|
+ if (num > awk->maxfield)
|
|
|
+ awk->maxfield = num;
|
|
|
+ /* Incomplete expression, a $ not followed by a number */
|
|
|
+ if (wrkbuf[1] == 0) {
|
|
|
+ fprintf(stderr, "Incomplete field descriptor at "
|
|
|
+ "or near character %d in awk pattern\n", i+1);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ /* Field separator */
|
|
|
+ case ',':
|
|
|
+ wrkbuf[offs++] = ' ';
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ /* if wrkbuf has nothing, we've got rubbish. Continue in the hopes
|
|
|
+ * that something else makes sense. */
|
|
|
+ if (offs == 0)
|
|
|
+ continue;
|
|
|
+ /* End of a field reached, put it into awk->fields */
|
|
|
+ wrkbuf[offs] = '\0';
|
|
|
+ awk->fields =
|
|
|
+ (char **)realloc(awk->fields, (awk->numfields + 1)
|
|
|
+ * sizeof(char *));
|
|
|
+ if (!awk->fields ) {
|
|
|
+ fprintf(stderr,
|
|
|
+ "Memory allocation error (awk->fields) in awkcomp()\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ awk->fields[awk->numfields] = strdup(wrkbuf);
|
|
|
+ if (!awk->fields[awk->numfields]) {
|
|
|
+ fprintf(stderr,
|
|
|
+ "Memory allocation error (awk->fields[%d]) in awkcomp()\n",
|
|
|
+ awk->numfields);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ memset(wrkbuf, 0, strlen(apattern) + 1);
|
|
|
+ awk->numfields++;
|
|
|
+ offs = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ free(wrkbuf);
|
|
|
+
|
|
|
+ if (awk->numfields == 0) {
|
|
|
+ fprintf(stderr,
|
|
|
+ "Unable to parse and compile the pattern; no fields found\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* awkfree() - free a previously allocated awk_pat structure */
|
|
|
+static void awkfree(awk_pat_t *awk )
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ for (i = 0; i < awk->numfields; i++)
|
|
|
+ free(awk->fields[i]);
|
|
|
+
|
|
|
+ free(awk->fields);
|
|
|
+}
|
|
|
+
|
|
|
+int main(int argc, char **argv)
|
|
|
+{
|
|
|
+ char *apattern = NULL, *gpattern = NULL;
|
|
|
+ char **files = NULL;
|
|
|
+ int numfiles = 0, i = 0, c = 0;
|
|
|
+ int ignore_case = 0, no_filename = 0, with_filename = 0, line_number = 0;
|
|
|
+
|
|
|
+ if (argc < 3) {
|
|
|
+ usage();
|
|
|
+ return EXIT_FAILURE;
|
|
|
+ }
|
|
|
+
|
|
|
+ tokdelim = strdup("\t\r\n ");
|
|
|
+ while (1) {
|
|
|
+ int opt_ind = 0;
|
|
|
+ while (c != -1) {
|
|
|
+ c = getopt_long(argc, argv, "wWhinF:", long_options, &opt_ind);
|
|
|
+ switch (c) {
|
|
|
+ case 'w':
|
|
|
+ with_filename = 0;
|
|
|
+ no_filename = 1;
|
|
|
+ break;
|
|
|
+ case 'i':
|
|
|
+ ignore_case = 1;
|
|
|
+ break;
|
|
|
+ case 'W':
|
|
|
+ with_filename = 1;
|
|
|
+ no_filename = 0;
|
|
|
+ break;
|
|
|
+ case 'n':
|
|
|
+ line_number = 1;
|
|
|
+ break;
|
|
|
+ case 'F':
|
|
|
+ tokdelim = realloc(tokdelim, 3 + strlen(optarg) + 1);
|
|
|
+ memset(tokdelim, 0, 3 + strlen( optarg ) + 1);
|
|
|
+ sprintf(tokdelim, "\t\r\n%s", optarg);
|
|
|
+ break;
|
|
|
+ case 'h':
|
|
|
+ usage();
|
|
|
+ free(tokdelim);
|
|
|
+ return EXIT_SUCCESS;
|
|
|
+ break;
|
|
|
+ case 'v':
|
|
|
+ printf("%s\n", VERSION);
|
|
|
+ free(tokdelim);
|
|
|
+ return EXIT_SUCCESS;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Now we'll grab our patterns and files. */
|
|
|
+ if ((argc - optind) < 2) {
|
|
|
+ usage();
|
|
|
+ free(tokdelim);
|
|
|
+ return EXIT_FAILURE;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* pattern one will be our "grep" pattern */
|
|
|
+ gpattern = strdup(argv[optind]);
|
|
|
+ if (gpattern == NULL) {
|
|
|
+ fprintf(stderr, "Memory allocation error");
|
|
|
+ exit(EXIT_FAILURE);
|
|
|
+ }
|
|
|
+ optind++;
|
|
|
+
|
|
|
+ /* pattern two is our "awk" pattern */
|
|
|
+ apattern = strdup(argv[optind]);
|
|
|
+ if(apattern == NULL) {
|
|
|
+ fprintf(stderr, "Memory allocation error");
|
|
|
+ exit(EXIT_FAILURE);
|
|
|
+ }
|
|
|
+ optind++;
|
|
|
+
|
|
|
+ /* Anything that remains is a file or wildcard which should be
|
|
|
+ * expanded by the calling shell. */
|
|
|
+ if (optind < argc) {
|
|
|
+ numfiles = argc - optind;
|
|
|
+ files = (char **)malloc(sizeof(char *) * (numfiles + 1));
|
|
|
+ for (i = 0; i < numfiles; i++) {
|
|
|
+ files[i] = strdup(argv[optind + i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* If the number of files is greater than 1 then we default to
|
|
|
+ * showing the filename unless specifically directed against it.*/
|
|
|
+ if (numfiles > 1 && no_filename == 0)
|
|
|
+ with_filename = 1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Process everything */
|
|
|
+ regex_t re;
|
|
|
+ int cflags = 0, rc = 0;
|
|
|
+
|
|
|
+ if (ignore_case)
|
|
|
+ cflags = REG_ICASE;
|
|
|
+ /* compile the regular expression parser */
|
|
|
+ if (regcomp(&re, gpattern, cflags)) {
|
|
|
+ fprintf(stderr,
|
|
|
+ "Error compiling grep-style pattern [%s]\n", gpattern);
|
|
|
+ return EXIT_FAILURE;
|
|
|
+ }
|
|
|
+
|
|
|
+ awk_pat_t awk;
|
|
|
+ if (awkcomp(&awk, apattern))
|
|
|
+ {
|
|
|
+ fprintf(stderr,
|
|
|
+ "Error compiling awk-style pattern [%s]\n", apattern);
|
|
|
+ return EXIT_FAILURE;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (numfiles > 0) {
|
|
|
+ if(process_files(
|
|
|
+ numfiles, files, re, awk, with_filename, line_number) == 0)
|
|
|
+ rc = 255; // We'll return 255 if no lines were found.
|
|
|
+ } else {
|
|
|
+ if(process_pipe(re, awk, line_number) == 0)
|
|
|
+ rc = 255;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Destructor */
|
|
|
+ for (i = 0; i < numfiles; i++) {
|
|
|
+ if (files[i])
|
|
|
+ free(files[i]);
|
|
|
+ }
|
|
|
+ free(files);
|
|
|
+
|
|
|
+ /* Awk pattern */
|
|
|
+ free(apattern);
|
|
|
+
|
|
|
+ /* Grep pattern */
|
|
|
+ free(gpattern);
|
|
|
+
|
|
|
+ /* Grep regex */
|
|
|
+ regfree(&re);
|
|
|
+
|
|
|
+ /* Awk pattern structure */
|
|
|
+ awkfree(&awk);
|
|
|
+
|
|
|
+ /* Token delimiter (might have been freed elsewhere) */
|
|
|
+ if (tokdelim)
|
|
|
+ free(tokdelim);
|
|
|
+ return rc;
|
|
|
+}
|