Parsing command-line arguments in C
To my knowledge, the three most popular ways how to parse command line arguments in C are:
- Getopt (
#include <unistd.h>
from the POSIX C Library), which can solve simple argument parsing tasks. If you're a bit familiar with bash, the getopt built-in of bash is based on Getopt from the GNU libc. - Argp (
#include <argp.h>
from the GNU C Library), which can solve more complex tasks and takes care of stuff like, for example:-?
,--help
for help message, including email address-V
,--version
for version information--usage
for usage message
- Doing it yourself, which I don't recommend for programs that would be given to somebody else, as there is too much that could go wrong or lower quality. The popular mistake of forgetting about '--' to stop option parsing is just one example.
The GNU C Library documentation has some nice examples for Getopt and Argp.
- http://www.gnu.org/software/libc/manual/html_node/Getopt.html
- http://www.gnu.org/software/libc/manual/html_node/Argp.html
Example for using Getopt
#include <stdbool.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main(int argc, char *argv[]){ bool isCaseInsensitive = false; int opt; enum { CHARACTER_MODE, WORD_MODE, LINE_MODE } mode = CHARACTER_MODE; while ((opt = getopt(argc, argv, "ilw")) != -1) { switch (opt) { case 'i': isCaseInsensitive = true; break; case 'l': mode = LINE_MODE; break; case 'w': mode = WORD_MODE; break; default: fprintf(stderr, "Usage: %s [-ilw] [file...]\n", argv[0]); exit(EXIT_FAILURE); } } // Now optind (declared extern int by <unistd.h>) is the index of the first non-option argument. // If it is >= argc, there were no non-option arguments. // ...}
Example for using Argp
#include <argp.h>#include <stdbool.h>const char *argp_program_version = "programname programversion";const char *argp_program_bug_address = "<your@email.address>";static char doc[] = "Your program description.";static char args_doc[] = "[FILENAME]...";static struct argp_option options[] = { { "line", 'l', 0, 0, "Compare lines instead of characters."}, { "word", 'w', 0, 0, "Compare words instead of characters."}, { "nocase", 'i', 0, 0, "Compare case insensitive instead of case sensitive."}, { 0 } };struct arguments { enum { CHARACTER_MODE, WORD_MODE, LINE_MODE } mode; bool isCaseInsensitive;};static error_t parse_opt(int key, char *arg, struct argp_state *state) { struct arguments *arguments = state->input; switch (key) { case 'l': arguments->mode = LINE_MODE; break; case 'w': arguments->mode = WORD_MODE; break; case 'i': arguments->isCaseInsensitive = true; break; case ARGP_KEY_ARG: return 0; default: return ARGP_ERR_UNKNOWN; } return 0;}static struct argp argp = { options, parse_opt, args_doc, doc, 0, 0, 0 };int main(int argc, char *argv[]){ struct arguments arguments; arguments.mode = CHARACTER_MODE; arguments.isCaseInsensitive = false; argp_parse(&argp, argc, argv, 0, 0, &arguments); // ...}
Example for Doing it Yourself
#include <stdbool.h>#include <stdio.h>#include <stdlib.h>int main(int argc, char *argv[]){ bool isCaseInsensitive = false; enum { CHARACTER_MODE, WORD_MODE, LINE_MODE } mode = CHARACTER_MODE; size_t optind; for (optind = 1; optind < argc && argv[optind][0] == '-'; optind++) { switch (argv[optind][1]) { case 'i': isCaseInsensitive = true; break; case 'l': mode = LINE_MODE; break; case 'w': mode = WORD_MODE; break; default: fprintf(stderr, "Usage: %s [-ilw] [file...]\n", argv[0]); exit(EXIT_FAILURE); } } argv += optind; // *argv points to the remaining non-option arguments. // If *argv is NULL, there were no non-option arguments. // ...}
Disclaimer: I am new to Argp, the example might contain errors.
I've found Gengetopt to be quite useful - you specify the options you want with a simple configuration file, and it generates a .c/.h pair that you simply include and link with your application. The generated code makes use of getopt_long, appears to handle most common sorts of command line parameters, and it can save a lot of time.
A gengetopt input file might look something like this:
version "0.1"package "myApp"purpose "Does something useful."# Optionsoption "filename" f "Input filename" string requiredoption "verbose" v "Increase program verbosity" flag offoption "id" i "Data ID" int requiredoption "value" r "Data value" multiple(1-) int optional
Generating the code is easy and spits out cmdline.h
and cmdline.c
:
$ gengetopt --input=myApp.cmdline --include-getopt
The generated code is easily integrated:
#include <stdio.h>#include "cmdline.h"int main(int argc, char ** argv) { struct gengetopt_args_info ai; if (cmdline_parser(argc, argv, &ai) != 0) { exit(1); } printf("ai.filename_arg: %s\n", ai.filename_arg); printf("ai.verbose_flag: %d\n", ai.verbose_flag); printf("ai.id_arg: %d\n", ai.id_arg); int i; for (i = 0; i < ai.value_given; ++i) { printf("ai.value_arg[%d]: %d\n", i, ai.value_arg[i]); }}
If you need to do any extra checking (such as ensuring flags are mutually exclusive), you can do this fairly easily with the data stored in the gengetopt_args_info
struct.
Use getopt()
, or perhaps getopt_long()
.
int iflag = 0;enum { WORD_MODE, LINE_MODE } op_mode = WORD_MODE; // Default setint opt;while ((opt = getopt(argc, argv, "ilw") != -1){ switch (opt) { case 'i': iflag = 1; break; case 'l': op_mode = LINE_MODE; break; case 'w': op_mode = WORD_MODE; break; default: fprintf(stderr, "Usage: %s [-ilw] [file ...]\n", argv[0]); exit(EXIT_FAILURE); }}/* Process file names or stdin */if (optind >= argc) process(stdin, "(standard input)", op_mode);else{ int i; for (i = optind; i < argc; i++) { FILE *fp = fopen(argv[i], "r"); if (fp == 0) fprintf(stderr, "%s: failed to open %s (%d %s)\n", argv[0], argv[i], errno, strerror(errno)); else { process(fp, argv[i], op_mode); fclose(fp); } } }
Note that you need to determine which headers to include (I make it 4 that are required), and the way I wrote the op_mode
type means you have a problem in the function process()
- you can't access the enumeration down there. It's best to move the enumeration outside the function; you might even make op_mode
a file-scope variable without external linkage (a fancy way of saying static
) to avoid passing it to the function. This code does not handle -
as a synonym for standard input, another exercise for the reader. Note that getopt()
automatically takes care of --
to mark the end of options for you.
I've not run any version of the typing above past a compiler; there could be mistakes in it.
For extra credit, write a (library) function:
int filter(int argc, char **argv, int idx, int (*function)(FILE *fp, const char *fn));
which encapsulates the logic for processing file name options after the getopt()
loop. It should handle -
as standard input. Note that using this would indicate that op_mode
should be a static file scope variable. The filter()
function takes argc
, argv
, optind
and a pointer to the processing function. It should return 0 (EXIT_SUCCESS) if it was able to open all the files and all invocations of the function reported 0, otherwise 1 (or EXIT_FAILURE). Having such a function simplifies writing Unix-style 'filter' programs that read files specified on the command line or standard input.