C Command-Line Tools with getopt: Parse Arguments Like a Linux Utility
Command-line programs are an important part of Linux and Unix-like systems. C provides argc and argv for basic argument processing, while the POSIX getopt function makes it easier to handle short command-line options such as -v, -f file, or -o output.txt.
What Are Command-Line Arguments?
Command-line arguments are values supplied to a program when it is started from a terminal. The main function receives them through argc and argv.
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Argument count: %d\n", argc);
for (int i = 0; i < argc; i++)
{
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
Understanding argc and argv
| Item | Meaning |
|---|---|
| argc | Number of command-line arguments |
| argv | Array of argument strings |
| argv[0] | Usually the program name or invocation path |
| argv[1] and later | Arguments supplied by the user |
A Simple Command-Line Program
Suppose a program named greet accepts a name from the command line.
#include <stdio.h>
int main(int argc, char *argv[])
{
if (argc < 2)
{
printf("Usage: %s <name>\n", argv[0]);
return 1;
}
printf("Hello, %s!\n", argv[1]);
return 0;
}
After compiling, a command such as ./greet Alice supplies Alice as argv[1].
Why Use getopt?
Manually processing argv becomes cumbersome when a program supports multiple options. getopt provides a standard POSIX interface for parsing short options such as -v, -n, -o filename, and combined options such as -abc.
Basic getopt Example
The following program supports -v for verbose output and -h for help.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int option;
while ((option = getopt(argc, argv, "vh")) != -1)
{
switch (option)
{
case 'v':
printf("Verbose mode enabled\n");
break;
case 'h':
printf("Usage: %s [-v] [-h]\n", argv[0]);
return 0;
default:
fprintf(stderr, "Usage: %s [-v] [-h]\n", argv[0]);
return 1;
}
}
return 0;
}
The getopt Option String
The third argument to getopt describes which short options the program accepts. A letter by itself represents a flag. A letter followed by a colon requires an argument.
| Option String | Meaning |
|---|---|
| v | Accept -v without an argument |
| o: | Accept -o followed by a required argument |
| vh | Accept -v and -h |
| v:o: | Accept -v with an optional argument only where supported by the implementation; o requires an argument |
Options with Arguments
When an option requires an argument, getopt makes that value available through the global variable optarg.
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int option;
while ((option = getopt(argc, argv, "o:")) != -1)
{
switch (option)
{
case 'o':
printf("Output file: %s\n", optarg);
break;
default:
fprintf(stderr, "Usage: %s [-o file]\n", argv[0]);
return 1;
}
}
return 0;
}
Using optind for Remaining Arguments
After getopt finishes processing options, optind points to the first argument that was not processed as an option.
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int option;
while ((option = getopt(argc, argv, "v")) != -1)
{
switch (option)
{
case 'v':
printf("Verbose mode\n");
break;
default:
return 1;
}
}
for (int i = optind; i < argc; i++)
{
printf("Input: %s\n", argv[i]);
}
return 0;
}
Handling Missing Option Arguments
If an option requires an argument but none is supplied, getopt reports an error. Programs should handle this case and display useful usage information.
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int option;
while ((option = getopt(argc, argv, "o:")) != -1)
{
switch (option)
{
case 'o':
printf("Output: %s\n", optarg);
break;
case '?':
fprintf(stderr, "Usage: %s [-o file]\n", argv[0]);
return 1;
}
}
return 0;
}
The ? Return Value
getopt normally returns '?' when it encounters an unknown option or a required option argument is missing, depending on the option-string configuration and implementation.
Suppressing getopt Error Messages
Setting opterr to zero prevents getopt from automatically printing its own diagnostic messages. This allows the application to produce its own consistent error messages.
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int option;
opterr = 0;
while ((option = getopt(argc, argv, "o:")) != -1)
{
if (option == '?')
{
fprintf(stderr, "Invalid option or missing argument.\n");
return 1;
}
}
return 0;
}
A Practical CLI Tool
A useful command-line utility might support verbose mode, an output file, and a help option.
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
static void printUsage(const char *program)
{
printf("Usage: %s [-v] [-o output] [input ...]\n", program);
printf(" -v Enable verbose mode\n");
printf(" -o FILE Select output file\n");
printf(" -h Show this help message\n");
}
int main(int argc, char *argv[])
{
bool verbose = false;
const char *output = NULL;
int option;
while ((option = getopt(argc, argv, "vo:h")) != -1)
{
switch (option)
{
case 'v':
verbose = true;
break;
case 'o':
output = optarg;
break;
case 'h':
printUsage(argv[0]);
return 0;
case '?':
printUsage(argv[0]);
return 1;
}
}
if (verbose)
{
printf("Verbose mode enabled\n");
}
if (output != NULL)
{
printf("Output: %s\n", output);
}
for (int i = optind; i < argc; i++)
{
printf("Input: %s\n", argv[i]);
}
return 0;
}
Short Options Can Be Combined
Short options that do not require arguments can often be combined. For example, -vh can represent -v -h.
./tool -vh
# Equivalent to
./tool -v -h
Using -- to End Option Processing
The -- marker is commonly used to tell command-line parsers that following arguments should be treated as operands rather than options. This is particularly useful when a filename begins with a hyphen.
./tool -- -input.txt
After option parsing stops, the remaining operands can be processed using argv starting at optind.
Parsing Numeric Arguments
Command-line option arguments arrive as strings. Use functions such as strtol to convert numeric input while also validating errors.
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
static int parseInt(const char *text, int *result)
{
char *end;
long value;
errno = 0;
value = strtol(text, &end, 10);
if (errno != 0 || end == text || *end != '\0' ||
value < INT_MIN || value > INT_MAX)
{
return 0;
}
*result = (int)value;
return 1;
}
A Tool with a Numeric Option
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int count = 1;
int option;
while ((option = getopt(argc, argv, "n:")) != -1)
{
switch (option)
{
case 'n':
{
char *end;
long value;
errno = 0;
value = strtol(optarg, &end, 10);
if (errno != 0 || end == optarg || *end != '\0' ||
value < 1 || value > INT_MAX)
{
fprintf(stderr, "Invalid count: %s\n", optarg);
return 1;
}
count = (int)value;
break;
}
default:
fprintf(stderr, "Usage: %s [-n count]\n", argv[0]);
return 1;
}
}
printf("Count: %d\n", count);
return 0;
}
Building and Running a getopt Program
cc -std=c17 -Wall -Wextra -Wpedantic tool.c -o tool
./tool -v -o result.txt input.txt
./tool -vh
./tool -- -input.txt
getopt on Linux and POSIX Systems
getopt is a POSIX interface and is widely available on Linux and other Unix-like systems. It is not part of the ISO C standard itself. Code intended to be strictly portable across all C implementations should account for this distinction.
getopt vs Manual argv Parsing
| Approach | Best For |
|---|---|
| argc / argv | Very small programs with simple arguments |
| getopt | Unix/Linux tools using short options |
| Custom parser | Specialized command-line grammars |
| Third-party CLI library | Large applications with advanced option systems |
Common Mistakes
- Forgetting to include unistd.h on POSIX systems
- Failing to handle unknown options
- Not checking for missing required option arguments
- Treating optarg as a numeric value without validating the conversion
- Forgetting that option arguments are strings
- Processing remaining operands without using optind
- Assuming getopt is part of ISO C
Best Practices
- Provide a clear usage message
- Handle unknown and malformed options explicitly
- Validate option arguments before using them
- Use strtol instead of atoi when robust numeric validation is required
- Keep command-line parsing separate from the program's core logic
- Use -- when filenames or operands may begin with a hyphen
- Return a nonzero exit status for command-line errors
Real-World Applications
- Linux administration utilities
- File-processing tools
- Build and deployment utilities
- System monitoring programs
- Backup tools
- Command-line data converters
- Developer utilities
Practice Exercises
- Create a program with -v and -h options
- Add an -o option that accepts an output filename
- Add a numeric -n option and validate its value
- Process multiple input filenames after the options
- Support combined short options such as -vh
- Handle an input filename beginning with a hyphen using --
- Build a small Linux-style command-line utility with a complete usage message
Conclusion
Command-line argument parsing is a fundamental skill for Linux C programming. argc and argv provide the basic building blocks, while getopt makes short-option parsing cleaner and more reliable. By combining getopt with validation, clear usage messages, and proper error handling, you can build C utilities that behave like professional command-line tools.