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 POSIXgrep‘s-h/-lbehaviour. - Exit code convention — returning 0 when at least one match is found and 1 when no matches are found matches the POSIX
grepconvention; pipelines likegrep pattern file && do_somethingrely on this. - Error handling per file — a
continueon failed open means a bad filename doesn’t abort the search of other valid files; errors go tostderr.
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 agrep()function keeps the loop trivial - Stdin as a file — treating
stdinas just anotherFILE *means the samegrep()function works for both cases without duplication strstrfor pattern matching — for literal substring searchstrstris sufficient; a fullgrepwould use regular expressions viaregcomp/regexec
Set Up Your C Environment
← Exercise 7-6 |
Chapter 7 Solutions |
Exercise 7-8 →
Book: The C Programming Language, 2nd Ed — Kernighan & Ritchie