#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#define MAX_FILE_LENGTH 500
// Gets the next line from the text file, returns the pointer to that line
char* getLine(char* loc, FILE* fileStream);
// Returns whether the given string contains the specified search pattern
int checkStringMatch(char* toSearch, char* pattern);
// Returns a lowercase copy of the string
char* switchToLower(char* string);
// Prints the given line to stdout, with lineNum if nonzero
void printLine(int lineNum, char* fileName, char* text);
// Processes a file, printing any lines in that file that match the given search pattern
void processfile(char* filename, char* pattern );
// Checks for possible options and returns the number of enabled options
int checkoptions(char** argv);
// Global variables hold option state
int optionn = 0;
int optioni = 0;
int main(int argc, char**argv)
{
// Make sure the minimum number of arguments is present
if(argc < 3)
{
fputs("Must provide a search pattern and at least 1 file.\n", stderr);
return 1;
}
// Number of enabled options, used to detemine where search pattern is in argv
int numoptions = checkoptions(argv);
// Holds the search pattern
char* pattern = argv[numoptions+ 1];
// If "-i" is used, change the pattern to lowercase
if(optioni)
{
pattern = switchToLower(pattern);
}
int i;
for(i=numoptions + 2; i < argc; i++)
{
char* filename = argv[i];
processfile(filename, pattern);
}
// the switchToLower function resulted in pattern being malloc'd
if(optioni)
{
free(pattern);
}
return 0;
}
int checkoptions(char** argv)
{
int numOptions = 0;
if(checkStringMatch(argv[1], "-n") || checkStringMatch(argv[2], "-n"))
{
optionn = 1;
numOptions++;
}
if(checkStringMatch(argv[1], "-i") || checkStringMatch(argv[2], "-i"))
{
optioni = 1;
numOptions++;
}
return numOptions;
}
void processfile(char* filename, char* pattern)
{
FILE* f = fopen(filename, "r");
// If the file does not exist print an error message and return, otherwise process it
if(!f)
{
fprintf(stderr, "File %s unopenable.\n", filename);
return;
}
// Will hold each line scanned in from the file
char* line = (char*)malloc(MAX_FILE_LENGTH*sizeof(char));
int lineNum = 1;
while(!feof(f))
{
line = getLine(line, f);
// Only proceed if the line is non-null
if(line)
{
// If the "-i" option is used compare the string after stripping case
if(optioni)
{
char* lowerLine = switchToLower(line);
if(checkStringMatch(lowerLine, pattern))
{
printLine(lineNum*optionn, filename, line);
}
free(lowerLine);
}
else
{
if(checkStringMatch(line, pattern))
{
printLine(lineNum*optionn, filename, line);
}
}
lineNum++;
}
}
// Close file and free line
fclose(f);
free(line);
}
char* getLine(char* loc, FILE* fileStream)
{
return fgets(loc, MAX_FILE_LENGTH, fileStream);
}
int checkStringMatch(char* toSearch, char* pattern)
{ char* exists = strstr(toSearch, pattern);
if(exists)
{
return 1;
}
return 0;
}
char* switchToLower(char* str)
{
char* newString = (char*)malloc(sizeof(char)*strlen(str));
//Iterate through charaters in str, switch each letter to lowercase
int i;
for(i = 0; str[i]; i++)
{
newString[i]=tolower(str[i]);
}
return newString;
}
void printLine(int lineNum, char* fileName, char* text)
{
if(lineNum)
{
printf("%d %s %s", lineNum, fileName, text);
}
else
{
printf("%s %s", fileName, text);
}
}
|
|
|||
|
Use of Malloc:Where you do:
You can also simplify this code:
to
Problems with
|
|
Yuushi covered pretty much everything, but a few more minor things stood out to me. I'm not a fan of the overuse of MAX_FILE_LENGTH. When you're setting up automatic duration buffers, sure, use For example:
Should probably be:
Then you'd call it like (assuming a stack allocated array):
Note that getline can now be used in situations where you have differing buffer sizes. There's no need to constrain your function to one very specific use. That brings me to my next point: I'm not a fan of your fgets loop. It's typically better to just loop directory on fgets:
I'm not a fan of the global option variables. In this situation you could easily make the case that they don't matter, but on a site that specializes in pedantic code criticisms, I'm obligated to point out that they're unnecessary and couple the functionality of some of some of your functions to magical hidden state. If you passed the options around, you could would be more flexible (it would also be marginally more cumbersome to write, but such is the woe of high quality code :)).
Speaking of misnomers, (Note: if you plan on extending checkStringMatch to do more than check substring existence, ignore this comment.) If you're going to comment your functions, you might as well use doc comments. They can be parsed by a lot of existing programs to make prettified documentation (well, API documentation, not true documentation). They also tend to be a bit easier to visually scan:
A lot of your pointers being passed around could point to constant characters. It's a lot clearer (and better guaranteed) that
That doesn't make sense. You should probably just use strcmp. You could, however, extend the use of strstr to support situations like Rather than printing straight to stderr in For example, if you wanted to add a
It would be easy to just use strstr inline anywhere you use checkStringMatch. When reading through your code, I had to look to see what checkStringMatch is, but I know what strstr is. The level of indirection can cause a bit of confusion (though checkStringContains would not). Since a NULL pointer is guaranteed to evaluate to false, you could even have the same usage: For example:
Could be:
If you plan to extend or change the functionality of checkStringMatch though, then this should obviously be ignored. This is 100% opinion, but |
|||
|
|
|
In addition to comments by others:
|
|||
|
|
