Jump to: navigation, search

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);
}
Personal tools