10.4. A pattern matching program

This section presents a complete program which makes use of option letters as program arguments to control the way it performs its job.

The program first processes any arguments that resemble options; the first argument which is not an option is remembered for use as a ‘search string’. Any remaining arguments are used to specify file names which are to be read as input to the program; if no file names are provided, the program reads from its standard input instead. If a match for the search string is found in a line of input text, that whole line is printed on the standard output.

The options function is used to process all option letters supplied to the program. This program recognises five options: -c, -i, -l, -n, and -v. None of these options is required to be followed by an option argument. When the program is run with one or more of these options its behaviour is modified as follows:

-c
the program prints a count of the total number of matching lines it found in the input file(s). No lines of text are printed.
-i
when searching for a match, the case of letters in both the input lines and string is ignored.
-l
each line of text printed on the output is prefixed with the line number being examined in the current input file.
-n
each line of text printed on the output is prefixed with the name of the file that contained the line.
-v
the program prints only lines which do not match the string supplied.

When the program finishes, it returns an exit status to indicate one of the following situations:

EXIT_SUCCESS
at least one match was found.
EXIT_FAILURE
no match was found, or some error occurred.

The program makes extensive use of standard library functions to do all of the hard work. For example, all of the file handling is performed by calls to stdio functions. Notice too that the real heart of the program, the string matching, is simply handled by a call to the strstr library function.

Here is the code for the whole program. Of course, to get this to work you would need to compile it together with the code for the options routine presented above.

/*
* Simple program to print lines from a text file which contain
* the "word" supplied on the command line.
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/*
* Declarations for the pattern program
*
*/

#define CFLAG 0x001     /* only count the number of matching lines */
#define IFLAG 0x002     /* ignore case of letters */
#define LFLAG 0x004     /* show line numbers */
#define NFLAG 0x008     /* show input file names */
#define VFLAG 0x010     /* show lines which do NOT match */

extern int OptIndex;    /* current index into argv[] */
extern char *OptArg;    /* global option argument pointer */

/*
* Fetch command line switches from arguments to main()
*/

int options(int, char **, const char *);

/*
* Record the required options ready to control program beaviour
*/

unsigned set_flags(int, char **, const char *);

/*
* Check each line of the input file for a match
*/

int look_in(const char *, const char *, unsigned);

/*
* Print a line from the input file on the standard output
* in the format specified by the command line switches
*/

void print_line(unsigned mask, const char *fname,
                int lnno, const char *text);


static const char
                /* Legal options for pattern */
        *OptString = "cilnv",
                /* message when options or arguments incorrect */
        *errmssg = "usage: pattern [-cilnv] word [filename]\n";

int main(int argc, char *argv[])
{
        unsigned flags = 0;
        int success = 0;
        char *search_string;

        if(argc < 2){
                fprintf(stderr, errmssg);
                exit(EXIT_FAILURE);
        }

        flags = set_flags(argc, argv, OptString);

        if(argv[OptIndex])
                search_string = argv[OptIndex++];
        else {
                fprintf(stderr, errmssg);
                exit(EXIT_FAILURE);
        }

        if(flags & IFLAG){
                /* ignore case by dealing only with lowercase */
                char *p;
                for(p = search_string ; *p ; p++)
                        if(isupper(*p))
                                *p = tolower(*p);
        }

        if(argv[OptIndex] == NULL){
                /* no file name given, so use stdin */
                success = look_in(NULL, search_string, flags);
        } else while(argv[OptIndex] != NULL)
                success += look_in(argv[OptIndex++],
                                search_string, flags);

        if(flags & CFLAG)
                printf("%d\n", success);

        exit(success ? EXIT_SUCCESS : EXIT_FAILURE);
}

unsigned set_flags(int argc, char **argv, const char *opts)
{
        unsigned flags = 0;
        int ch = 0;

        while((ch = options(argc, argv, opts)) != -1){
                switch(ch){
                        case 'c':
                                flags |= CFLAG;
                                break;
                        case 'i':
                                flags |= IFLAG;
                                break;
                        case 'l':
                                flags |= LFLAG;
                                break;
                        case 'n':
                                flags |= NFLAG;
                                break;
                        case 'v':
                                flags |= VFLAG;
                                break;
                        case '?':
                                fprintf(stderr, errmssg);
                                exit(EXIT_FAILURE);
                }
        }
        return flags;
}


int look_in(const char *infile, const char *pat, unsigned flgs)
{
        FILE *in;
        /*
         * line[0] stores the input line as read,
         * line[1] is converted to lower-case if necessary
         */
        char line[2][BUFSIZ];
        int lineno = 0;
        int matches = 0;

        if(infile){
                if((in = fopen(infile, "r")) == NULL){
                        perror("pattern");
                        return 0;
                }
        } else
                in = stdin;

        while(fgets(line[0], BUFSIZ, in)){
                char *line_to_use = line[0];
                lineno++;
                if(flgs & IFLAG){
                        /* ignore case */
                        char *p;
                        strcpy(line[1], line[0]);
                        for(p = line[1] ; *p ; *p++)
                                if(isupper(*p))
                                        *p = tolower(*p);
                        line_to_use = line[1];
                }

                if(strstr(line_to_use, pat)){
                        matches++;
                        if(!(flgs & VFLAG))
                                print_line(flgs, infile, lineno, line[0]);
                } else if(flgs & VFLAG)
                        print_line(flgs, infile, lineno, line[0]);
        }
        fclose(in);
        return matches;
}

void print_line(unsigned mask, const char *fname,
                        int lnno, const char *text)
{
        if(mask & CFLAG)
                return;
        if(mask & NFLAG)
                printf("%s:", *fname ? fname : "stdin");
        if(mask & LFLAG)
                printf(" %d :", lnno);
        printf("%s", text);
}
Example 10.3