Command-line argument parsing is a fundamental skill for C programmers. Whether you're building simple utilities or complex applications, properly handling command-line arguments separates professional tools from quick scripts. This comprehensive guide covers everything from basic argc/argv handling to advanced parsing with getopt and beyond.
The Basics: argc and argv
Every C program begins with the main function, which receives command-line arguments:
int main(int argc, char *argv[]) {
// argc: argument count (number of arguments)
// argv: argument vector (array of argument strings)
return 0;
}
argcis at least 1 (the program name)argv[0]is the program nameargv[argc]is always NULL
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Program: %s\n", argv[0]);
printf("Number of arguments: %d\n", argc - 1);
for (int i = 1; i < argc; i++) {
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}
Manual Argument Parsing
1. Simple Argument Processing
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <name> [age] [height]\n", argv[0]);
return 1;
}
char *name = argv[1];
int age = 0;
double height = 0.0;
// Optional arguments
if (argc > 2) {
age = atoi(argv[2]);
}
if (argc > 3) {
height = atof(argv[3]);
}
printf("Name: %s\n", name);
if (age > 0) printf("Age: %d\n", age);
if (height > 0) printf("Height: %.2f\n", height);
return 0;
}
2. Flag Parsing
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
typedef struct {
bool verbose;
bool quiet;
bool help;
char *input_file;
char *output_file;
int threads;
} Options;
void print_usage(const char *progname) {
printf("Usage: %s [options] <input>\n", progname);
printf("Options:\n");
printf(" -v, --verbose Enable verbose output\n");
printf(" -q, --quiet Suppress output\n");
printf(" -o <file> Output file\n");
printf(" -t <num> Number of threads (default: 1)\n");
printf(" -h, --help Show this help\n");
}
int parse_args_manual(int argc, char *argv[], Options *opts) {
// Default values
opts->verbose = false;
opts->quiet = false;
opts->help = false;
opts->input_file = NULL;
opts->output_file = NULL;
opts->threads = 1;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
opts->verbose = true;
}
else if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0) {
opts->quiet = true;
}
else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
opts->help = true;
return 0;
}
else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: -o requires an argument\n");
return -1;
}
opts->output_file = argv[++i];
}
else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--threads") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: -t requires an argument\n");
return -1;
}
opts->threads = atoi(argv[++i]);
if (opts->threads <= 0) {
fprintf(stderr, "Error: threads must be positive\n");
return -1;
}
}
else if (argv[i][0] == '-') {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
return -1;
}
else {
// First non-option argument is input file
if (opts->input_file == NULL) {
opts->input_file = argv[i];
} else {
fprintf(stderr, "Unexpected argument: %s\n", argv[i]);
return -1;
}
}
}
return 0;
}
int main(int argc, char *argv[]) {
Options opts;
if (parse_args_manual(argc, argv, &opts) < 0) {
print_usage(argv[0]);
return 1;
}
if (opts.help || opts.input_file == NULL) {
print_usage(argv[0]);
return 0;
}
printf("Input file: %s\n", opts.input_file);
if (opts.output_file) printf("Output file: %s\n", opts.output_file);
printf("Threads: %d\n", opts.threads);
if (opts.verbose) printf("Verbose mode enabled\n");
if (opts.quiet) printf("Quiet mode enabled\n");
return 0;
}
Using getopt() for POSIX-style Options
1. Basic getopt() Usage
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
int main(int argc, char *argv[]) {
int opt;
int verbose = 0;
int threads = 1;
char *output_file = NULL;
// getopt options string:
// 'v' - no argument
// 'o:' - requires argument
// 't::' - optional argument (GNU extension)
while ((opt = getopt(argc, argv, "vho:t::")) != -1) {
switch (opt) {
case 'v':
verbose = 1;
break;
case 'o':
output_file = optarg;
break;
case 't':
if (optarg) {
threads = atoi(optarg);
} else {
threads = 2; // Default if no argument
}
break;
case 'h':
printf("Usage: %s [-v] [-o file] [-t [n]] <input>\n", argv[0]);
return 0;
case '?':
// getopt prints error for unknown options
return 1;
}
}
// Process non-option arguments
if (optind >= argc) {
fprintf(stderr, "Expected input file\n");
return 1;
}
char *input_file = argv[optind];
printf("Input file: %s\n", input_file);
if (output_file) printf("Output file: %s\n", output_file);
printf("Threads: %d\n", threads);
if (verbose) printf("Verbose mode\n");
return 0;
}
2. Advanced getopt() with Error Handling
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <ctype.h>
typedef struct {
int verbose;
int quiet;
int threads;
char *input_file;
char *output_file;
char *config_file;
int port;
char *host;
} Config;
void print_usage(const char *progname) {
printf("Usage: %s [OPTIONS] <input-file>\n", progname);
printf("Options:\n");
printf(" -v, --verbose Increase verbosity\n");
printf(" -q, --quiet Suppress warnings\n");
printf(" -o, --output=FILE Output file\n");
printf(" -c, --config=FILE Config file\n");
printf(" -t, --threads=N Number of threads (default: 1)\n");
printf(" -p, --port=N Port number\n");
printf(" -h, --host=HOST Host address\n");
printf(" --help Show this help\n");
printf(" --version Show version\n");
}
int parse_options(int argc, char *argv[], Config *cfg) {
// Default values
cfg->verbose = 0;
cfg->quiet = 0;
cfg->threads = 1;
cfg->input_file = NULL;
cfg->output_file = NULL;
cfg->config_file = NULL;
cfg->port = 0;
cfg->host = NULL;
// Long options structure
static struct option long_options[] = {
{"verbose", no_argument, 0, 'v'},
{"quiet", no_argument, 0, 'q'},
{"output", required_argument, 0, 'o'},
{"config", required_argument, 0, 'c'},
{"threads", required_argument, 0, 't'},
{"port", required_argument, 0, 'p'},
{"host", required_argument, 0, 'h'},
{"help", no_argument, 0, 256},
{"version", no_argument, 0, 257},
{0, 0, 0, 0}
};
int opt;
int option_index = 0;
// Disable getopt's default error messages
opterr = 0;
while ((opt = getopt_long(argc, argv, "vqo:c:t:p:h:",
long_options, &option_index)) != -1) {
switch (opt) {
case 'v':
cfg->verbose++;
break;
case 'q':
cfg->quiet = 1;
break;
case 'o':
cfg->output_file = optarg;
break;
case 'c':
cfg->config_file = optarg;
break;
case 't':
cfg->threads = atoi(optarg);
if (cfg->threads <= 0) {
fprintf(stderr, "Error: threads must be positive\n");
return -1;
}
break;
case 'p':
cfg->port = atoi(optarg);
if (cfg->port <= 0 || cfg->port > 65535) {
fprintf(stderr, "Error: invalid port number\n");
return -1;
}
break;
case 'h':
cfg->host = optarg;
break;
case 256: // --help
print_usage(argv[0]);
exit(0);
case 257: // --version
printf("Version 1.0.0\n");
exit(0);
case '?':
if (optopt) {
fprintf(stderr, "Error: option '-%c' requires an argument\n", optopt);
} else {
fprintf(stderr, "Error: unknown option '%s'\n", argv[optind - 1]);
}
return -1;
default:
return -1;
}
}
// Check for input file
if (optind < argc) {
cfg->input_file = argv[optind];
optind++;
// Warn about extra arguments
if (optind < argc) {
fprintf(stderr, "Warning: extra arguments ignored: ");
while (optind < argc) {
fprintf(stderr, "%s ", argv[optind++]);
}
fprintf(stderr, "\n");
}
}
return 0;
}
int main(int argc, char *argv[]) {
Config cfg;
if (parse_options(argc, argv, &cfg) < 0) {
return 1;
}
if (!cfg.input_file) {
fprintf(stderr, "Error: input file required\n");
print_usage(argv[0]);
return 1;
}
// Display configuration
printf("Configuration:\n");
printf(" Input file: %s\n", cfg.input_file);
if (cfg.output_file) printf(" Output file: %s\n", cfg.output_file);
if (cfg.config_file) printf(" Config file: %s\n", cfg.config_file);
printf(" Threads: %d\n", cfg.threads);
printf(" Verbosity level: %d\n", cfg.verbose);
if (cfg.quiet) printf(" Quiet mode\n");
if (cfg.port) printf(" Port: %d\n", cfg.port);
if (cfg.host) printf(" Host: %s\n", cfg.host);
return 0;
}
GNU getopt_long with Optional Arguments
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
// Custom parser for optional arguments
int parse_optional_int(const char *arg, int default_value) {
if (arg == NULL) return default_value;
return atoi(arg);
}
int main(int argc, char *argv[]) {
static struct option long_options[] = {
{"verbose", no_argument, 0, 'v'},
{"output", required_argument, 0, 'o'},
{"level", optional_argument, 0, 'l'},
{"color", optional_argument, 0, 'c'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int verbose = 0;
char *output = NULL;
int level = 1;
const char *color = "auto";
int opt;
int option_index = 0;
// GNU extension: 'l::' means optional argument
while ((opt = getopt_long(argc, argv, "vo:l::c::h",
long_options, &option_index)) != -1) {
switch (opt) {
case 'v':
verbose = 1;
break;
case 'o':
output = optarg;
break;
case 'l':
level = optarg ? atoi(optarg) : 5;
break;
case 'c':
if (optarg) {
color = optarg;
} else {
color = "always";
}
break;
case 'h':
printf("Usage: %s [options]\n", argv[0]);
printf(" -v, --verbose\n");
printf(" -o, --output=FILE\n");
printf(" -l, --level[=N] (default: 1, with arg: 5)\n");
printf(" -c, --color[=WHEN] (auto, always, never)\n");
return 0;
case '?':
return 1;
}
}
printf("Verbose: %s\n", verbose ? "yes" : "no");
printf("Output: %s\n", output ? output : "(none)");
printf("Level: %d\n", level);
printf("Color: %s\n", color);
return 0;
}
Using argp (GNU argp Parser)
1. Basic argp Example
#include <argp.h>
#include <stdio.h>
#include <stdlib.h>
// Program documentation
static char doc[] = "A sample program using argp";
static char args_doc[] = "INPUT_FILE";
// Option definitions
static struct argp_option options[] = {
{"verbose", 'v', 0, 0, "Produce verbose output"},
{"output", 'o', "FILE", 0, "Output to FILE"},
{"threads", 't', "NUM", 0, "Number of threads (default: 1)"},
{0}
};
// Program arguments structure
struct arguments {
char *args[1]; // INPUT_FILE
int verbose;
char *output_file;
int threads;
};
// Parse a single option
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
struct arguments *arguments = state->input;
switch (key) {
case 'v':
arguments->verbose = 1;
break;
case 'o':
arguments->output_file = arg;
break;
case 't':
arguments->threads = arg ? atoi(arg) : 1;
if (arguments->threads <= 0) {
argp_error(state, "threads must be positive");
return EINVAL;
}
break;
case ARGP_KEY_ARG:
if (state->arg_num >= 1) {
argp_error(state, "too many arguments");
return ARGP_ERR_UNKNOWN;
}
arguments->args[state->arg_num] = arg;
break;
case ARGP_KEY_END:
if (state->arg_num < 1) {
argp_error(state, "input file required");
return ARGP_ERR_UNKNOWN;
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
// argp parser
static struct argp argp = {options, parse_opt, args_doc, doc};
int main(int argc, char *argv[]) {
struct arguments arguments;
// Default values
arguments.verbose = 0;
arguments.output_file = NULL;
arguments.threads = 1;
arguments.args[0] = NULL;
// Parse arguments
error_t err = argp_parse(&argp, argc, argv, 0, 0, &arguments);
if (err) {
fprintf(stderr, "Error parsing arguments\n");
return 1;
}
printf("Input file: %s\n", arguments.args[0]);
printf("Verbose: %s\n", arguments.verbose ? "yes" : "no");
printf("Output file: %s\n", arguments.output_file ? arguments.output_file : "(none)");
printf("Threads: %d\n", arguments.threads);
return 0;
}
2. Advanced argp with Subcommands
#include <argp.h>
#include <stdio.h>
#include <string.h>
// Command structure
struct command {
const char *name;
void (*func)(int argc, char **argv);
const char *doc;
};
// Command functions
static void cmd_add(int argc, char **argv) {
int sum = 0;
for (int i = 0; i < argc; i++) {
sum += atoi(argv[i]);
}
printf("Sum: %d\n", sum);
}
static void cmd_mul(int argc, char **argv) {
int product = 1;
for (int i = 0; i < argc; i++) {
product *= atoi(argv[i]);
}
printf("Product: %d\n", product);
}
// Command table
static struct command commands[] = {
{"add", cmd_add, "Add numbers"},
{"mul", cmd_mul, "Multiply numbers"},
{NULL, NULL, NULL}
};
// Global arguments
struct arguments {
int verbose;
char *command;
int argc;
char **argv;
};
static char doc[] = "A command-line calculator";
static char args_doc[] = "COMMAND [NUMBERS...]";
static struct argp_option options[] = {
{"verbose", 'v', 0, 0, "Verbose output"},
{0}
};
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
struct arguments *arguments = state->input;
switch (key) {
case 'v':
arguments->verbose = 1;
break;
case ARGP_KEY_ARG:
if (arguments->command == NULL) {
arguments->command = arg;
} else {
// Collect remaining args for command
arguments->argc = state->arg_num;
arguments->argv = &state->argv[state->next - 1];
}
break;
case ARGP_KEY_END:
if (arguments->command == NULL) {
argp_error(state, "command required");
return ARGP_ERR_UNKNOWN;
}
// Validate command
int found = 0;
for (struct command *cmd = commands; cmd->name; cmd++) {
if (strcmp(cmd->name, arguments->command) == 0) {
found = 1;
break;
}
}
if (!found) {
argp_error(state, "unknown command: %s", arguments->command);
return ARGP_ERR_UNKNOWN;
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = {options, parse_opt, args_doc, doc};
int main(int argc, char *argv[]) {
struct arguments arguments;
arguments.verbose = 0;
arguments.command = NULL;
arguments.argc = 0;
arguments.argv = NULL;
argp_parse(&argp, argc, argv, 0, 0, &arguments);
// Execute command
for (struct command *cmd = commands; cmd->name; cmd++) {
if (strcmp(cmd->name, arguments.command) == 0) {
if (arguments.verbose) {
printf("Executing command: %s\n", cmd->name);
}
cmd->func(arguments.argc, arguments.argv);
break;
}
}
return 0;
}
Advanced Pattern: Subcommand Parsing
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
typedef struct {
const char *name;
int (*handler)(int argc, char **argv);
const char *help;
} Command;
// Command handlers
static int cmd_init(int argc, char **argv) {
int quiet = 0;
char *template = NULL;
// Parse init-specific options
int opt;
while ((opt = getopt(argc, argv, "qt:")) != -1) {
switch (opt) {
case 'q':
quiet = 1;
break;
case 't':
template = optarg;
break;
default:
return -1;
}
}
printf("Initializing...\n");
if (template) printf("Using template: %s\n", template);
if (quiet) printf("Quiet mode\n");
return 0;
}
static int cmd_build(int argc, char **argv) {
int jobs = 1;
int verbose = 0;
int opt;
while ((opt = getopt(argc, argv, "j:v")) != -1) {
switch (opt) {
case 'j':
jobs = atoi(optarg);
break;
case 'v':
verbose = 1;
break;
default:
return -1;
}
}
printf("Building with %d job(s)\n", jobs);
if (verbose) printf("Verbose output\n");
return 0;
}
static int cmd_clean(int argc, char **argv) {
int force = 0;
int opt;
while ((opt = getopt(argc, argv, "f")) != -1) {
switch (opt) {
case 'f':
force = 1;
break;
default:
return -1;
}
}
printf("Cleaning...\n");
if (force) printf("Force mode\n");
return 0;
}
// Command table
static Command commands[] = {
{"init", cmd_init, "Initialize a new project"},
{"build", cmd_build, "Build the project"},
{"clean", cmd_clean, "Clean build artifacts"},
{NULL, NULL, NULL}
};
void print_global_help(const char *progname) {
printf("Usage: %s <command> [options]\n", progname);
printf("\nCommands:\n");
for (Command *cmd = commands; cmd->name; cmd++) {
printf(" %-10s %s\n", cmd->name, cmd->help);
}
printf("\nGlobal options:\n");
printf(" -h, --help Show help for command\n");
}
void print_command_help(const char *progname, const char *cmdname) {
printf("Usage: %s %s [options]\n", progname, cmdname);
if (strcmp(cmdname, "init") == 0) {
printf("Options:\n");
printf(" -q Quiet mode\n");
printf(" -t <template> Template to use\n");
} else if (strcmp(cmdname, "build") == 0) {
printf("Options:\n");
printf(" -j <jobs> Number of parallel jobs\n");
printf(" -v Verbose output\n");
} else if (strcmp(cmdname, "clean") == 0) {
printf("Options:\n");
printf(" -f Force clean\n");
}
}
int main(int argc, char *argv[]) {
if (argc < 2) {
print_global_help(argv[0]);
return 1;
}
// Check for help first
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
print_global_help(argv[0]);
return 0;
}
// Find command
const char *cmdname = argv[1];
Command *cmd = NULL;
for (Command *c = commands; c->name; c++) {
if (strcmp(c->name, cmdname) == 0) {
cmd = c;
break;
}
}
if (!cmd) {
fprintf(stderr, "Unknown command: %s\n", cmdname);
print_global_help(argv[0]);
return 1;
}
// Check for command-specific help
if (argc > 2 && (strcmp(argv[2], "-h") == 0 || strcmp(argv[2], "--help") == 0)) {
print_command_help(argv[0], cmdname);
return 0;
}
// Execute command
return cmd->handler(argc - 1, argv + 1);
}
Environment Variable Support
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
typedef struct {
int verbose;
int threads;
char *config_dir;
char *cache_dir;
} Config;
// Convert string to uppercase for environment variable names
void str_toupper(char *str) {
for (; *str; str++) {
*str = toupper(*str);
}
}
// Get configuration from environment variables
void get_config_from_env(Config *cfg) {
char *env_val;
// Program name prefix for environment variables
const char *prefix = "MYAPP_";
char env_name[64];
snprintf(env_name, sizeof(env_name), "%sVERBOSE", prefix);
env_val = getenv(env_name);
if (env_val) {
cfg->verbose = atoi(env_val) > 0;
}
snprintf(env_name, sizeof(env_name), "%sTHREADS", prefix);
env_val = getenv(env_name);
if (env_val) {
cfg->threads = atoi(env_val);
}
snprintf(env_name, sizeof(env_name), "%sCONFIG_DIR", prefix);
env_val = getenv(env_name);
if (env_val) {
cfg->config_dir = strdup(env_val);
}
snprintf(env_name, sizeof(env_name), "%sCACHE_DIR", prefix);
env_val = getenv(env_name);
if (env_val) {
cfg->cache_dir = strdup(env_val);
}
}
// Command line overrides environment
void parse_args_with_env(int argc, char *argv[], Config *cfg) {
// First get defaults from environment
get_config_from_env(cfg);
// Then override with command line
int opt;
while ((opt = getopt(argc, argv, "vt:c:")) != -1) {
switch (opt) {
case 'v':
cfg->verbose = 1;
break;
case 't':
cfg->threads = atoi(optarg);
break;
case 'c':
free(cfg->config_dir);
cfg->config_dir = strdup(optarg);
break;
}
}
}
int main(int argc, char *argv[]) {
Config cfg = {0, 4, NULL, NULL}; // Defaults
parse_args_with_env(argc, argv, &cfg);
printf("Configuration:\n");
printf(" Verbose: %s\n", cfg.verbose ? "yes" : "no");
printf(" Threads: %d\n", cfg.threads);
printf(" Config dir: %s\n", cfg.config_dir ? cfg.config_dir : "(default)");
printf(" Cache dir: %s\n", cfg.cache_dir ? cfg.cache_dir : "(default)");
free(cfg.config_dir);
free(cfg.cache_dir);
return 0;
}
Error Handling and Validation
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <limits.h>
typedef struct {
int port;
int workers;
char *host;
char *log_file;
int timeout;
} ServerConfig;
// Safe integer parsing with validation
int parse_int(const char *str, int min, int max, int *result) {
char *endptr;
errno = 0;
long val = strtol(str, &endptr, 10);
if (errno != 0 || endptr == str || *endptr != '\0') {
return -1; // Invalid number
}
if (val < min || val > max) {
return -2; // Out of range
}
*result = (int)val;
return 0;
}
// File existence check
int file_exists(const char *path) {
FILE *f = fopen(path, "r");
if (f) {
fclose(f);
return 1;
}
return 0;
}
int parse_server_args(int argc, char *argv[], ServerConfig *cfg) {
// Default values
cfg->port = 8080;
cfg->workers = 4;
cfg->host = NULL;
cfg->log_file = NULL;
cfg->timeout = 30;
int opt;
while ((opt = getopt(argc, argv, "p:w:h:l:t:")) != -1) {
switch (opt) {
case 'p': {
int port;
int ret = parse_int(optarg, 1, 65535, &port);
if (ret < 0) {
fprintf(stderr, "Error: invalid port number '%s'\n", optarg);
return -1;
}
cfg->port = port;
break;
}
case 'w': {
int workers;
int ret = parse_int(optarg, 1, 100, &workers);
if (ret < 0) {
fprintf(stderr, "Error: invalid worker count '%s'\n", optarg);
return -1;
}
cfg->workers = workers;
break;
}
case 'h':
cfg->host = optarg;
break;
case 'l':
// Check if log directory exists
cfg->log_file = optarg;
break;
case 't': {
int timeout;
int ret = parse_int(optarg, 1, 3600, &timeout);
if (ret < 0) {
fprintf(stderr, "Error: invalid timeout '%s'\n", optarg);
return -1;
}
cfg->timeout = timeout;
break;
}
case '?':
return -1;
}
}
// Validate log file directory
if (cfg->log_file) {
char *last_slash = strrchr(cfg->log_file, '/');
if (last_slash) {
*last_slash = '\0';
if (!file_exists(cfg->log_file)) {
fprintf(stderr, "Error: log directory '%s' does not exist\n",
cfg->log_file);
return -1;
}
*last_slash = '/';
}
}
// Validate host format (simple check)
if (cfg->host) {
// Could add more validation (IP address format, hostname rules)
}
return 0;
}
int main(int argc, char *argv[]) {
ServerConfig cfg;
if (parse_server_args(argc, argv, &cfg) < 0) {
fprintf(stderr, "Usage: %s [-p port] [-w workers] [-h host] [-l logfile] [-t timeout]\n",
argv[0]);
return 1;
}
printf("Server configuration:\n");
printf(" Port: %d\n", cfg.port);
printf(" Workers: %d\n", cfg.workers);
printf(" Host: %s\n", cfg.host ? cfg.host : "(all interfaces)");
printf(" Log file: %s\n", cfg.log_file ? cfg.log_file : "(none)");
printf(" Timeout: %d seconds\n", cfg.timeout);
return 0;
}
Tab Completion Support
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Generate bash completion script
void generate_bash_completion(const char *progname) {
printf("_%s_completion() {\n", progname);
printf(" local cur prev opts\n");
printf(" COMPREPLY=()\n");
printf(" cur=\"${COMP_WORDS[COMP_CWORD]}\"\n");
printf(" prev=\"${COMP_WORDS[COMP_CWORD-1]}\"\n");
printf(" opts=\"-v -o -t --verbose --output --threads --help\"\n");
printf("\n");
printf(" case \"${prev}\" in\n");
printf(" -o|--output)\n");
printf(" # Complete files\n");
printf(" COMPREPLY=( $(compgen -f -- ${cur}) )\n");
printf(" return 0\n");
printf(" ;;\n");
printf(" -t|--threads)\n");
printf(" # Complete numbers\n");
printf(" COMPREPLY=( $(compgen -W \"1 2 4 8 16\" -- ${cur}) )\n");
printf(" return 0\n");
printf(" ;;\n");
printf(" esac\n");
printf("\n");
printf(" if [[ ${cur} == -* ]] ; then\n");
printf(" COMPREPLY=( $(compgen -W \"${opts}\" -- ${cur}) )\n");
printf(" return 0\n");
printf(" fi\n");
printf("}\n");
printf("complete -F _%s_completion %s\n", progname, progname);
}
int main(int argc, char *argv[]) {
if (argc == 2 && strcmp(argv[1], "--completion") == 0) {
generate_bash_completion(argv[0]);
return 0;
}
// Normal argument parsing...
return 0;
}
Best Practices Summary
- Always provide help: Include
-hor--helpoptions - Validate input: Check ranges, file existence, etc.
- Use consistent option styles: Support both short and long options
- Handle errors gracefully: Provide clear error messages
- Support environment variables: Allow configuration via environment
- Document options: Include usage information
- Be POSIX-compliant: Follow standard conventions
- Consider subcommands: For complex tools
- Test edge cases: Empty arguments, missing values, etc.
- Provide completion: Generate shell completion scripts
Common Option Patterns
// Boolean flags "-v", "--verbose" // Enable verbose mode "-q", "--quiet" // Suppress output "--no-color" // Disable colors // Options with arguments "-o file", "--output=file" // Output file "-t N", "--threads=N" // Number of threads // Optional arguments (GNU style) "--color[=WHEN]" // Color mode (auto/always/never) // Multiple values "-I /usr/include" // Can appear multiple times "-D KEY=VALUE" // Define macro // Subcommands "git commit -m 'message'" // Command + subcommand "docker run -it ubuntu" // Subcommand with options
Conclusion
Command-line argument parsing in C ranges from simple manual parsing to sophisticated systems using getopt, argp, or custom subcommand handlers. The right approach depends on your application's complexity:
- Simple tools: Manual parsing or basic getopt
- Standard utilities: getopt with long options
- Complex applications: argp or custom subcommand systems
- Professional tools: Support environment variables and tab completion
By following the patterns and practices in this guide, you can create command-line tools that are intuitive, robust, and professional. Remember that good argument parsing is the first interaction users have with your tool—make it count.