For those who don’t know grep is a program on many unix-based computers
(i.e. MacOS, Linux, FreeBSD) that can be used for seaching files for patterns.
$> grep ward ./path/to/file.txt
# outputs:
afterward he had a word with the suspect.
thinking towards a new way to prevent crime.
awarded a medal for his bravery
Recently I’ve been diving back into systems programming and as an exercise to
refamiliarize myself with systems programming. To that end I decided to
implement a very basic version of grep.
I dubbed it minigrep, not because I’m super creative, but because I had done
a similar exercise in rust while reading the The Rust Programming
Language1 and I borrowed the name.
I don’t and won’t be using A.I. for this type of project because I’m trying to learn. A.I. ruins that in my experience.
Assumptions.
I’m assuming you know how to compile a C program. It isn’t hard and I trust you can go figure out how to do it. I’m not going to go into getting a compiler on your machine either becuase, frankly, if you are serious about making software, as a hobby or otherwise, you need to be able to go look up how to do something like this. You can do it. I had to do the same thing.
I’m also assuming you are in some empty directory on your computer waiting to start. If you don’t know how to do that you are not ready to write C programs.
Defining The Problem.
Before we continue, let’s pause to think about what grep is and does. If we are
going to implement a version of, even a very basic one, we will need to know
what it is. The manpage for it describes it as this, please note that I have
edited for clarity:
grep searches for PATTERNS in each FILE. PATTERNS is one or more patterns separated by newline characters, and grep prints each line that matches a pattern.
Requirements
From the above description I can derive the requirements ror my minigrep.
I need the following data:
- a pattern
- a file
And, I need to do the following:
- loop through each line in a file.
- check if the pattern exists in the line.
- if yes, print it.
Starting Point
The first thing you will have to do is create a file with code in it. I usually
start with a simple “hello world” in a file called main.c:
#include <stdio.h>
int main() {
printf("Hello world\n");
return 0;
}
For every unit of change I make I will usually recompile and run it. I will omit that process here, just know that I am doing it while I make each change.
Expected Output
Hello world
Accessing Arguments
I could hard code a file and different args starting out, but that is not what
I want to do. I am simply going to tap into the arguments that get fed to my
program. I do this by adding argc and argv to my main function.
// snip ...
- int main()
+ int main(int argc, char * argv[])
{
- printf("Hello world\n");
+ printf("argc=%d, argv[0]=%s\n", argc, argv[0]);
return 0;
}
argc is the count of arguments I am getting. argv is a list or “array” of
those arguments as strings or character arrays. The first argument in the
argv list is always the program getting run. the rest depend on what you type
into your terminal.
Expected Output
argc will vary depending on how many whitespace seperated words you input.
argc=1, argv[0]=./minigrep
Adding Helpful Usage Messages
Thinking back to my requirements I know two arguments are going to be
required. The pattern and the file to search. That means I’ll need a minimum of
three arguments. It’s three because the first argument passed to a program is
the program itself. I’m going to use that requirement to define a helpful
message that explains how to use the minigrep program:
int main(int argc, char * argv[])
{
+ if (argc < 3) {
+ printf("Usage: %s <pattern> <path>\n", argv[0]);
+ return 1;
+ }
- printf("argc=%d, argv[0]=%s\n", argc, argv[0]);
return 0;
}
Expected Output
If I run my program by itself or just one argument a message will print.
Usage: ./minigrep <pattern> <path>
If I pass enough arguments I will get no output.
Storing Arguments For Use Later
Now that I am getting my arguments or an error message, I want to store the arguments I need somewhere in my program. I’m going to bake in a couple of assumptions:
- The pattern will come first.
- The pattern will not be anything more complicated than a substring.
- the file will come second.
- Any other arguments I don’t care about and will be ignored.
int main(int argc, char * argv[])
{
if (argc < 3) {
printf("Usage: %s <pattern> <path>\n", argv[0]);
return 1;
}
+ char * pattern = argv[1];
+ char * path = argv[2];
+ printf("pattern=%s, path=%s\n", query, path);
return 0;
}
I store my pattern and path args as char * because that is what argv is a
list of char * argv[] or in english: an array of strings (char arrays).
Expected Output
pattern=mypattern, path=./main.c
Opening the File
Next we have to open a file and handle errors. Also we have to make sure we close
the file when we are done. Please note I am using // ... snip to save space. Do
not remove the code I have hidden in my example.
int main(int argc, char * argv[])
{
// ... snip
- char * pattern = argv[1];
- char * path = argv[2];
- printf("pattern=%s, path=%s\n", query, path);
+ FILE * file = fopen(path, "r");
+ if (!file) {
+ printf("error opening file");
+ return 1;
+ }
+ fclose(file);
return 0;
}
Expected Output
if I pass a file that doesn’t exist or the path to the file is wrong I should see:
error opening file
otherwise no output.
Printing Each Line In The File
We’re getting close. Now I simply want to print each line in my file.
int main(int argc, char * argv[])
{
// ... snip
char * pattern = argv[1];
char * path = argv[2];
printf("pattern=%s, path=%s\n", query, path);
FILE * file = fopen(path, "r");
if (!file) {
printf("error opening file");
return 1;
}
- fclose(file);
+ char line[1024];
+ while (fgets(line, sizeof(line), file)) {
+ printf("%s", line);
+ }
+ fclose(file);
return 0;
}
Notice that I moved the fclose to the end of the main function. I do not want
to close my file before I print out it’s contents.
Additionally I am using a character array as a buffer. I’ve arbitrarily chosen
1024 as my length of the character array. It is possible that this is not
enough for some files. In this example I know it will be enough.
This is something I will have to return to and refactor in a future post.
Expected Output
I am omitting the output because when you run the program it should just print the
contents of your main.c file. At least, that’s what I was using. If you are using
some other file, the output should match the contents of the file.
Checking Lines for the Pattern
Now comes the last part. I need to compare my line and pattern query. Recall
that I am assuming for this program that my pattern will be some substring. The
C standard library has a function for matching substrings to strings called
strstr.
#include <stdio.h>
+#include <string.h>
int main(int argc, char * argv[])
{
// ... snip
char line[1024];
while (fgets(line, sizeof(line), file)) {
- printf("%s", line);
+ if (strstr(line, query)) {
+ printf("%s", line);
+ }
}
// ... snip
}
This will only print out lines that have the matching pattern.
Example Output
$>./minigrep printf ./main.c
printf("Usage: %s <pattern> <path>\n", argv[0]);
printf("error opening file");
printf("%s", line);
Conclusion
There you have it, a very basic implementation of grep. There is much more that can be done to create a more complete copy. I am hoping to return to this program in future posts2 to address a few different things:
- case insensitive search.
- create a Makefile.
- more robust pattern search using regular expressions.
- refactoring.
- a config type.
Just to name a few.
Here is the final code for the file.
#include <stdio.h>
#include <string.h>
int main(int argc, char * argv[])
{
if (argc < 3) {
printf("Usage: %s <pattern> <path>\n", argv[0]);
return 1;
}
char * query = argv[1];
char * path = argv[2];
FILE * file = fopen(path, "r");
if (!file) {
printf("error opening file");
return 1;
}
char line[1024];
while (fgets(line, sizeof(line), file)) {
if (strstr(line, query)) {
printf("%s", line);
}
}
fclose(file);
return 0;
}
Specifically in chapter 12 of the book. ↩︎
This article is continued in Improving minigrep ↩︎

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.