C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
187: Parsing Options with getopt
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:
- Use a
switchstatement inside awhile(getopt(...))loop. - Correctly configure the
optstringto distinguish between flags with and without arguments. - Use
optindto iterate through and print any remaining filenames passed at the end of the command. - 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
There are no comments for now.