diff options
-rw-r--r-- | ch7/7-07_pattern-with-file.c | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/ch7/7-07_pattern-with-file.c b/ch7/7-07_pattern-with-file.c new file mode 100644 index 0000000..46732c4 --- /dev/null +++ b/ch7/7-07_pattern-with-file.c @@ -0,0 +1,128 @@ +#include <stdio.h> +#include <string.h> + +/* The C Programming Language: 2nd Edition + * + * Exercise 7-7: Modify the pattern finding program of Chapter 5 to take its + * input from a set of named files or, if no files are named as arguments, from + * the standard input. Should the file name be printed when a matching line is + * found? + * + * Notes: The program in question is on page 117 of the book. Note from various + * errata documents (found around the Web) that the line: + * + * while (c = *++argv[0]) + * + * is not correct, since it's attempting to modify something that the standard + * makes no guarantee to be modifiable. So to monkey-patch it, I used the + * typical 'i is the iterator' variable. + * + * The biggest obstacle to this exercise was correcting the original code from + * page 117. + * + * A gotcha that I came across is figuring out how to make use of or save the + * pattern that the user will be searching for. I could have allocated a + * character array (string) and stored it directly, but it made more sense to + * create another character pointer and leverage the storage that argv was + * already making use of. So I ended up with two pointers to the same data, + * like hardlinks in a *nix filesystem. + * + * The filename that a pattern is found in *should* be present, because it's + * trivial to match text in multiple files. Generally when one is looking for + * text, they want to *edit* that text, and editing it is fruitless if you + * don't have the file name handy. That said, putting it behind an option is a + * great way to add extensibility to it. + */ + +#define MAXLINE 1000 + +int get_line(char *line, int max, FILE *f); + +int main(int argc, char *argv[]) { + char line[MAXLINE]; + long lineno = 0; + int c, except = 0, number = 0, showfiles = 0, found = 0, i = 0; + char *pattern = NULL; + char *filename = NULL; + FILE *infile = NULL; + if (argc < 2) { + printf("pwf: please specify a pattern to search for\n"); + return 1; + } + while (--argc > 0 && (*++argv)[0] == '-') { + i = 0; + while ((c = (*argv)[++i]) != '\0') { + switch (c) { + case 'x': + except = 1; + break; + case 'n': + number = 1; + break; + case 'f': + showfiles = 1; + break; + case 'h': + printf("Usage: pwf [-{xnfh}] PATTERN [file] [file] ...\n"); + break; + default: + printf("pwf: illegal option %c\n", c); + argc = 0; + found = -1; + break; + } + } + } + /* The first argument _after_ options should be the pattern; without + * a pattern we shouldn't be accepting anything at all. + */ + pattern = *argv; + /* This handles the edge-case of no file names given. It's somewhat of an + * ugly hack since it fools argc, but it gets the job done. */ + if (argc == 1) { + infile = stdin; + filename = "stdin"; + argc++; + } + while (--argc > 0) { + /* Now we handle the case of a given filename */ + if (infile != stdin) { + filename = *(++argv); + infile = fopen(filename, "r"); + if (filename == NULL || infile == NULL) { + printf("File %s could not be opened; falling back to stdin...\n", filename); + infile = stdin; + filename = "stdin"; + } + } + /* We have _some_ usable file to work with, now. Let's do the thing */ + lineno = 0; + while (get_line(line, MAXLINE, infile) > 0) { + lineno++; + if ((strstr(line, pattern) != NULL) != except) { + if (showfiles) { + printf("%s:", filename); + } + if (number) { + printf("%ld:", lineno); + } + printf("%s", line); + found++; + } + } + } + return found; +} + +int get_line(char *s, int lim, FILE *f) { + int c, i; + for (i = 0; i < lim - 1 && (c = getc(f)) != EOF && c != '\n'; ++i) { + *s++ = c; + } + if (c == '\n') { + *s++ = c; + i++; + } + *s = '\0'; + return i; +} |