Skip to Content
Course content

187: Parsing Options with getopt

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this happen to almost every developer the first time they try to build a professional CLI tool in C. You want your program to feel like a real Unix utility—taking flags like -v for verbose or -f for a filename—so you reach for getopt. But then, you run the code, and it just... doesn't work. The flags are ignored, or your program crashes when you try to access the argument.

Take a look at this snippet. I'm trying to build a simple tool that reads a specific file passed via the -f flag.

#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    int opt;
    char *filename = NULL;

    while ((opt = getopt(argc, argv, "vf")) != -1) {
        switch (opt) {
            case 'v':
                printf("Verbose mode enabled\n");
                break;
            case 'f':
                filename = optarg; 
                printf("File set to: %s\n", filename);
                break;
            default:
                fprintf(stderr, "Usage: %s [-v] [-f filename]\n", argv[0]);
                return 1;
        }
    }
    return 0;
}

The disappearing filename

If you compile this and run ./mytool -f data.txt, you'll notice something frustrating: it doesn't print "File set to: data.txt". In fact, it might not print anything at all, or optarg might be NULL, leading to a segmentation fault if you tried to strlen() it.

The bug isn't in the switch statement; it's in the optstring: "vf". When you pass that string to getopt, you're telling the function, "I expect an optional 'v' flag and an optional 'f' flag, and neither of them takes any extra data."

Because getopt thinks -f is a standalone boolean flag, it doesn't look at the next element in argv (the "data.txt" part) to populate optarg. It just sees -f, matches it, and moves on. optarg stays NULL, and your filename is lost in the void.

The magic of the colon

To fix this, you have to use the "colon convention." In the optstring, any character followed by a colon tells getopt that the option requires an argument.

Change the loop initialization to this:

while ((opt = getopt(argc, argv, "vf:")) != -1) {

Notice the : after the f. Now, when getopt encounters -f, it knows it can't just return 'f' and be done. It grabs the very next string in the argument list and assigns its address to the global variable optarg. If the user provides -f without a filename, getopt will now automatically print an error message and return '?', which hits your default case and shuts down the program gracefully.

Handling the leftovers with optind

One thing that usually trips people up is what happens after the flags. Let's say you want your tool to take flags, but then also take a list of files at the end: ./mytool -v -f config.txt file1.txt file2.txt.

The while loop handles the flags, but it doesn't handle the trailing arguments. This is where optind comes in. optind is an external index maintained by getopt. After the loop finishes, optind points to the index of the first non-option argument in argv.

Here is the complete, robust pattern I use for most of my C utilities:

#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    int opt;
    char *filename = NULL;
    int verbose = 0;

    // 'v' is a flag, 'f:' requires an argument
    while ((opt = getopt(argc, argv, "vf:")) != -1) {
        switch (opt) {
            case 'v':
                verbose = 1;
                break;
            case 'f':
                filename = optarg;
                break;
            default:
                // getopt already printed an error to stderr
                return 1;
        }
    }

    if (verbose) printf("Verbose mode on\n");
    if (filename) printf("Processing file: %s\n", filename);

    // Process the remaining non-option arguments
    printf("Remaining arguments:\n");
    for (int i = optind; i < argc; i++) {
        printf("Arg %d: %s\n", i, argv[i]);
    }

    return 0;
}

I prefer this approach because it separates the configuration of the program (the flags) from the data the program operates on (the trailing arguments). It's cleaner, it's standard, and it prevents you from having to manually slice argv using complex pointer arithmetic.




📋 Practical Task

Build a Custom Log Filter Utility

Your task is to create a program called logfilter that simulates a log analysis tool. The program should parse the following command-line options using getopt:

  • -s [string]: A search string. The program should print "Searching for: [string]". (Required argument)
  • -n [number]: The number of lines to display. The program should print "Limit set to: [number] lines". (Required argument)
  • -i: A case-insensitive flag. The program should print "Case-insensitive mode enabled". (No argument)

After parsing the options, the program should print any remaining non-option arguments (the "target log files") on separate lines.

Requirements:

  1. Use a switch statement inside a while(getopt(...)) loop.
  2. Correctly configure the optstring to distinguish between flags with and without arguments.
  3. Use optind to iterate through and print any remaining filenames passed at the end of the command.
  4. Ensure the program prints a usage message if an invalid option is provided.

Test Case:
./logfilter -i -s "ERROR" -n 50 system.log auth.log
Expected Output:
Case-insensitive mode enabled
Searching for: ERROR
Limit set to: 50 lines
system.log
auth.log

Rating
0 0

There are no comments for now.

to be the first to leave a comment.