K&R C Programs Exercise 7-7

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, from the standard input. Should the file name be printed when a match is found?

Yes — when searching multiple files the filename should prefix each matching line (the same convention as Unix grep). When reading from a single file or stdin, the filename is omitted. The key change: wrap the search loop in a file-iteration loop; when argc == 2 (pattern only), search stdin.

Solution

/* K&R Exercise 7-7 — grep: pattern search in files or stdin
 * Compile: gcc -ansi -Wall ex7-7.c -o grep7
 * Usage:   ./grep7 pattern [file ...]
 *          ./grep7 hello *.txt
 *          echo "hello world" | ./grep7 hello */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAXLINE 1000

/* Search file fp for lines matching pattern.
 * Print matching lines; prefix with filename if show_name is non-zero.
 * Returns count of matching lines. */
static int grep(const char *pattern, FILE *fp,
                const char *filename, int show_name)
{
    char line[MAXLINE];
    int  matches = 0;

    while (fgets(line, MAXLINE, fp)) {
        if (strstr(line, pattern)) {
            if (show_name)
                printf("%s:", filename);
            printf("%s", line);
            /* add newline if line has none (last line of file) */
            if (line[strlen(line)-1] != '\n')
                putchar('\n');
            matches++;
        }
    }
    return matches;
}

int main(int argc, char *argv[])
{
    FILE *fp;
    int   total = 0;
    int   show_name;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s pattern [file ...]\n", argv[0]);
        return 1;
    }

    /* show filename only when searching more than one file */
    show_name = (argc > 3);

    if (argc == 2) {
        /* no files: read stdin */
        total = grep(argv[1], stdin, "(stdin)", 0);
    } else {
        int i;
        for (i = 2; i < argc; i++) {
            if ((fp = fopen(argv[i], "r")) == NULL) {
                fprintf(stderr, "%s: can't open %s\n", argv[0], argv[i]);
                continue;
            }
            total += grep(argv[1], fp, argv[i], show_name);
            fclose(fp);
        }
    }

    return (total > 0) ? 0 : 1;
}

Compile and Test

gcc -ansi -Wall ex7-7.c -o grep7

# Single file (no filename prefix)
printf "hello world\ngoodbye\nhello again\n" > /tmp/g1.txt
./grep7 hello /tmp/g1.txt

# Multiple files (filename prefix shown)
printf "no match here\n" > /tmp/g2.txt
./grep7 hello /tmp/g1.txt /tmp/g2.txt

# Stdin
echo "hello from stdin" | ./grep7 hello

Expected Output

hello world
hello again

/tmp/g1.txt:hello world
/tmp/g1.txt:hello again

hello from stdin

Design Notes

  • When to show the filename — with exactly one file (argc == 3) the filename is redundant; with two or more files (argc > 3) it’s essential. This mirrors POSIX grep‘s -h/-l behaviour.
  • Exit code convention — returning 0 when at least one match is found and 1 when no matches are found matches the POSIX grep convention; pipelines like grep pattern file && do_something rely on this.
  • Error handling per file — a continue on failed open means a bad filename doesn’t abort the search of other valid files; errors go to stderr.

What This Exercise Teaches

  • File argument iteration — the argv[2..argc-1] loop is the standard pattern for tools that accept a list of files; extracting the work into a grep() function keeps the loop trivial
  • Stdin as a file — treating stdin as just another FILE * means the same grep() function works for both cases without duplication
  • strstr for pattern matching — for literal substring search strstr is sufficient; a full grep would use regular expressions via regcomp/regexec

Set Up Your C Environment

← Exercise 7-6  | 
Chapter 7 Solutions  | 
Exercise 7-8 →

Book: The C Programming Language, 2nd Ed — Kernighan & Ritchie

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>