This article assumes you read my article that covered a very basic
implementation of the grep tool called minigrep. I figure most readers
aren’t interested in going back to read it, so here where we left our code last:
#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;
}
Plan
We are going to make the following refactors to our program:
We could do more than this, but that is the nature of many projects: There is always more to do. Something that I struggle with occasionally is determining where the line of diminishing returns is. For this article we will focus on these.
Let’s begin.
Define A Config Structure.
I want to create a data type that will describe and hold the options for my program.
Most languages have their own ways of defining types, but in C the method I will
be using is the struct or “structure”.
#include <stdio.h>
#include <string.h>
+ typedef struct {
+ const char * query;
+ const char * path;
+ } Config;
int main(int argc, char * argv[])
{
// ... snip
To put it simply I just created a structure called Config and it has two fields
or attributes: The pattern I am searching for and the path of the file I am searching.
In my starting code I had these pieces of information in their own variables.
// ... snip
char * query = argv[1];
char * path = argv[2];
// ... snip
You may be asking “why bother?” and it’s a fair question. I simply like grouping the config into a struct because, in a way, it’s self documenting. In a small program like this one, maybe that isn’t much of a value-add. In bigger projects it will be important. So I am just practicing good habits.
One last note, I could have defined my struct like this:
struct Config {
const char * query;
const char * path;
};
It’s perfectly valid to do that, but then I would have to use struct Config
any time I am trying to use it.
Using The Config.
Now that I have a struct I need to go and use it in the rest of my code.
// ...snip
typedef struct {
char * query;
char * path;
} Config;
int main(int argc, char * argv[])
{
if (argc < 3) { // ... snip }
- char * pattern = argv[1];
- char * path = argv[2];
+ Config config = { .query = argv[1], .path = argv[2] };
- FILE * file = fopen(path, "r");
+ FILE * file = fopen(config.path, "r");
if (!file) {
printf("error opening file");
return 1;
}
char line[1024];
while (fgets(line, sizeof(line), file)) {
- if (strstr(line, query)) {
+ if (strstr(line, config.query)) {
printf("%s", line);
}
}
// ... snip
}
Wrap Search Into A Function.
Next I want to move my searching logic into a function. This function is going
to need two arguments: the query and the file contents. Note that I am designating
my query as const This is just to denote that I am not going to modify the
query in the function. Think of it as saying “this argument is readonly”.
+void search(const char * query, FILE * contents)
+{
+}
Then I’m simply going to move my while loop that does the searching into the body of that function:
void search (const char * query, FILE * contents)
{
+ char line[1024];
- while (fgets(line, sizeof(line), file)) {
+ while (fgets(line, sizeof(line), contents)) {
- if (strstr(line, config.query)) {
+ if (strstr(line, query)) {
+ printf("%s", line);
+ }
+ }
}
int main(int argc, char * argv[])
{
// ... snip
- char line[1024];
- while (fgets(line, sizeof(line), file)) {
- if (strstr(line, config.query)) {
- printf("%s", line);
- }
- }
+ search(config.query, file);
// ... snip
}
Print Errors To stderr
Currently when I have some problem I just print it to output with printf with
everything else, particularly my results if I have any. That’s not what I want.
Instead I want to send my errors to a different output called stderr. This
way if I am sending my results to a file, for example, my errors are not
mingled in with them.
#include <stdio.h>
#include <string.h>
typedef struct {
char * query;
char * path;
} Config;
void search(char * const query, FILE * contents)
{
char line[1024];
while (fgets(line, sizeof(line), contents)) {
if (strstr(line, query)) {
printf("%s", line);
}
}
}
int main(int argc, char * argv[])
{
if (argc < 3) {
- printf("Usage: %s <pattern> <path>\n", argv[0]);
+ fprintf(stderr, "Usage: %s <pattern> <path>\n", argv[0]);
return 1;
}
Config config = { .query = argv[1], .path = argv[2] };
FILE * file = fopen(config.path, "r");
if (!file) {
- printf("error opening file");
+ perror("fopen");
return 1;
}
search(config.query, file);
fclose(file);
return 0;
}
Conclusion
That wraps up this next version of the minigrep tool.
Some additional changes I could make, and cover in a future article:
- add case insensitive search
- add line numbers to output
Here is the final code:
#include <stdio.h>
#include <string.h>
typedef struct {
const char * query;
const char * path;
} Config;
void search(const char * query, FILE * contents)
{
char line[1024];
while (fgets(line, sizeof(line), contents)) {
if (strstr(line, query)) {
printf("%s", line);
}
}
}
int main(int argc, char * argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s <pattern> <path>\n", argv[0]);
return 1;
}
Config config = { .query = argv[1], .path = argv[2] };
FILE * file = fopen(config.path, "r");
if (!file) {
perror("fopen");
return 1;
}
search(config.query, file);
fclose(file);
return 0;
}

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