The C Programming Language, 2nd Edition, by Kernighan and Ritchie
Exercise 7.07 on page 165
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?
Solution by Barrett Drawdy
/* * K&R2 exercise 7-7 By: Barrett Drawdy * * Modify the pattern finding program of Chapter 5 (on page 117) 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 * file is found? */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXFILES 10 /* maximum number of files to search in */ #define MAXLINE 1024 /* longest line that can be read at once + 1 for '\0' */ struct file { FILE *p; char *name; }; int main(int argc, char *argv[]) { struct file files[MAXFILES + 1]; struct file *fp = files; char **argp; char *pat; char line[MAXLINE]; int c; int found = 0, except = 0, number = 0; long line_num; if(argc < 2) { fprintf(stderr, "usage: %s -x -n [file1] [file2] ... pattern\n", argv[0]); exit(-1); } /* get pattern */ pat = argv[--argc]; /* open files and read arguments */ for(argp = argv + 1; argp - argv < argc; ++argp) { /* read arguments */ if(*argp[0] == '-') { while((c = *++argp[0])) switch(c) { case 'x': except = 1; break; case 'n': number = 1; break; default: fprintf(stderr, "%s: illegal option %c\n", argv[0], c); fprintf(stderr, "usage: %s -x -n [file1] [file2] ... pattern\n", argv[0]); exit(-1); } } /* read filenames */ else { if(fp - files >= MAXFILES) { fprintf(stderr, "%s: can only open %d files\n", argv[0], MAXFILES); exit(-1); } if((fp->p = fopen(*argp, "r")) == NULL) { fprintf(stderr, "%s: error opening %s\n", argv[0], *argp); exit(-1); } else fp++->name = *argp; } } /* if there were no filenames, read from stdin */ if(fp == files) { fp++->p = stdin; } fp->p = NULL; /* put NULL pointer at end of array */ /* search for pattern in each file */ for(fp = files; fp->p != NULL; ++fp) { line_num = 0; while(fgets(line, MAXLINE, fp->p) != NULL) { ++line_num; if((strstr(line, pat) != NULL) != except) { if(fp->p != stdin) printf("%s ", fp->name); if(number) printf("%ld", line_num); if(number || fp->p != stdin) putchar(':'); puts(line); ++found; } } } /* clean up */ for(fp = files; fp->p != NULL; ++fp) fclose(fp->p); return found; } /* end of main */
Solution by Jose G. López (Category 0)
/* Pattern finding program that takes its input from a set of file names or, if they aren't passed as arguments, from the standard input. The file name is shown when a matching line is found. Note: I've decided to implement the program using a list of structures, allocating memory when needed. */ #include <stdio.h> #include <stdlib.h> #include <string.h> struct file *getinput(int argc, char *argv[]); void find(char *pattern, int number, int except, struct file *p); struct file { FILE *fp; char *name; struct file *next; }; int main(int argc, char *argv[]) { int c, except = 0, number = 0; struct file *p; while (--argc > 0 && (*++argv)[0] == '-') { while ((c = *++argv[0])) { switch (c) { case 'n': number = 1; break; case 'x': except = 1; break; default: printf("incorrect option '%c'\n", c); argc = 0; break; } } } if (argc < 1) printf("Usage: find -x -n [FILE]... pattern\n"); else { p = getinput(argc, argv); find(argv[argc - 1], number, except, p); } return 0; } #define MAXLEN 1000 /* max length of any input line */ struct file *getfile(char *filename, struct file *aux); struct file *falloc(void); /* getinput: gets passed arguments and returns the first element in the list created. */ struct file *getinput(int argc, char *argv[]) { struct file *head, *p, *aux; aux = NULL; if (argc > 1) { /* if file names specified */ do { /* we don't have a list without a head */ head = getfile(*argv++, aux); argc--; } while (argc > 1 && head == NULL); aux = head; /* because we can't change its value inside getfile */ while (argc-- > 1) { if ((p = getfile(*argv++, aux)) != NULL) aux = p; } } else { /* read from input */ if ((head = falloc()) != NULL) { head->fp = stdin; head->name = "input"; head->next = NULL; } } return head; } /* getfile: with a file name tries to open the file and creates a node with a pointer to it and its name. */ struct file *getfile(char *filename, struct file *aux) { FILE *file; struct file *p; p = NULL; if ((file = fopen(filename, "r")) != NULL) { if ((p = falloc()) != NULL) { p->fp = file; p->name = filename; p->next = NULL; if (aux != NULL) { aux->next = p; } } } else fprintf(stderr, "Can't opent file %s\n", filename); return p; } /* find: searches the pattern in the list of structures starting from head. */ void find(char *pattern, int number, int except, struct file *p) { char line[MAXLEN]; long lineno; int show_fname; while (p != NULL) { lineno = 0; show_fname = 1; while (fgets(line, MAXLEN, p->fp) != NULL) { lineno++; if ((strstr(line, pattern) != NULL) != except) { if (show_fname) { /* show file name only once */ printf("%s\n", p->name); printf("--------------------------\n"); show_fname = 0; } if (number) printf("%ld:", lineno); printf("%s", line); } } if (!show_fname) printf("\n"); fclose(p->fp); p = p->next; } } /* falloc: allocates a new struct file node and returns a pointer to it. */ struct file *falloc(void) { return (struct file *)malloc(sizeof(struct file)); }
Solution by codybartfast (Category 0)
Adds an extra option to print the filename.
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXLINE 1000 enum errors { NOTHING_FOUND = -1, NO_ERROR = 0, ILLEGAL_OPTION, NO_PATTERN, FILE_OPEN_ERROR, FILE_READ_ERROR }; struct options { int except; int filename; int number; char *pattern; int pathargs; }; static void parseargs(int argc, char *argv[], struct options *); static int find(FILE *file, char *path, struct options *); static FILE *chkfopen(char *file, char *modes); static char *chkfgets(char *s, int n, FILE *iop, char *path); static char errormsg[MAXLINE]; int main(int argc, char *argv[]) { struct options options; int i, found = 0; char *path; FILE *file; parseargs(argc, argv, &options); if (options.pathargs < argc) for (i = options.pathargs; i < argc; i++) { path = argv[i]; file = chkfopen(path, "r"); found += find(file, path, &options); fclose(file); } else found = find(stdin, "<stdin>", &options); exit(found ? NO_ERROR : NOTHING_FOUND); } int find(FILE *file, char *path, struct options *opts) { char line[MAXLINE]; long lineno = 0, found = 0; while ((chkfgets(line, MAXLINE, file, path)) != NULL) { lineno++; if ((strstr(line, opts->pattern) != NULL) != opts->except) { if (opts->filename) printf("%s:", path); if (opts->number) printf("%ld:", lineno); printf("%s", line); found++; } } return found; } void parseargs(int argc, char *argv[], struct options *options) { char c, *arg; int ai; options->except = options->number = options->filename = 0; for (ai = 1; ai < argc && (arg = argv[ai])[0] == '-'; ai++) while ((c = *++arg)) switch (c) { case 'x': options->except = 1; break; case 'n': options->number = 1; break; case 'f': options->filename = 1; break; default: fprintf(stderr, "find: illegal option %c", c); exit(ILLEGAL_OPTION); } if (ai == argc) { fprintf(stderr, "Usage: find -x -n -f pattern\n"); exit(NO_PATTERN); } else { options->pattern = argv[ai++]; options->pathargs = ai; } } FILE *chkfopen(char *path, char *modes) { FILE *fp = fopen(path, modes); if (fp != NULL) return fp; sprintf(errormsg, "error: Failed to open file: '%s'", path); perror(errormsg); exit(FILE_OPEN_ERROR); } char *chkfgets(char *s, int n, FILE *iop, char *path) { char *r = fgets(s, n, iop); if (!ferror(iop)) return r; sprintf(errormsg, "error: Failed to read file: '%s'", path); perror(errormsg); fclose(iop); exit(FILE_READ_ERROR); }
Latest code on github
Solution by anonymous
I took the code from page 117 and updated it to handle a variable number of command line arguments. Plus, I changed -x to -v to match grep's switch for inversing the matching algorithm. I updated getline to accept FILE pointers and this program prints the matched file name if input is not stdin.
Here is my code
#include <stdio.h> #include <string.h> #include <stdlib.h> /* 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? */ #define MAXLEN 1000 #define MAXFILES 100 int getline(char *line, int max, FILE *fp); FILE *fp[MAXFILES] = { 0 }; // array of FILE pointers initialized to null int names[MAXFILES]; int fpIndex = 0; // print lines that match pattern. Source can be files or stdin int main(int argc, char *argv[]) { char line[MAXLEN], pattern[MAXLEN] = { 0 }; unsigned long lineNum = 0; int invert = 0, number = 0, found = 0, files = 0; for (int i = 1; i < argc; i++) if (strcmp(argv[i], "-v") == 0) { invert = 1; files = 0; } else if (strcmp(argv[i], "-n") == 0) { number = 1; files = 0; } else if (strcmp(argv[i], "-f") == 0) files = 1; else if (i + 1 == argc) strncpy(pattern, argv[i], MAXLEN - 1); // strncpy doesn't guarantee the string will be null terminated, so don't bother copying the last char from argv else if (files == 1) // add file pointer { if (fpIndex < MAXFILES) { if ((fp[fpIndex] = fopen(argv[i], "r")) == NULL) { fprintf(stderr, "can't open %s\n", argv[i]); exit(1); } names[fpIndex++] = i; // capture the index of the file name to use for printing later } else { fprintf(stderr, "too many input files, max supported is %d\n", MAXFILES); exit(1); } } else { fprintf(stderr, "usage: find [-v] [-n] [-f file1 [file2] [file3] [...]] pattern\n"); exit(1); } if (pattern[0] == '\0' || (files == 1 && fp[0] == NULL)) // no pattern found or no file pointers despite the provided -f argument { fprintf(stderr, "usage: find [-v] [-n] [-f file1 [file2] [file3] [...]] pattern\n"); exit(1); } if (fpIndex == 0) // if no input files, default to stdin fp[fpIndex] = stdin; for (int i = 0; i < MAXFILES && fp[i] != NULL; i++) { lineNum = 0; while (getline(line, MAXLEN, fp[i]) > 0) { lineNum++; if ((strstr(line, pattern) != NULL) != invert) // strstr(s, t) returns a pointer to the first occurrence of the string t in the string s, or NULL if there is none. { if (fpIndex > 0) // files were provided { if (number) printf("%s %03ld: %s", argv[names[i]], lineNum, line); else printf("%s: %s", argv[names[i]], line); } else { if (number) printf("%03ld: ", lineNum); printf("%s", line); } found++; } } } return found; } // read a line, return length int getline(char *line, int max, FILE *fp) { if (fgets(line, max, fp) == NULL) return 0; else return strlen(line); }