Building Professional Command-Line Tools: A Complete Guide to Argument Parsing in C

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;
}
  • argc is at least 1 (the program name)
  • argv[0] is the program name
  • argv[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

  1. Always provide help: Include -h or --help options
  2. Validate input: Check ranges, file existence, etc.
  3. Use consistent option styles: Support both short and long options
  4. Handle errors gracefully: Provide clear error messages
  5. Support environment variables: Allow configuration via environment
  6. Document options: Include usage information
  7. Be POSIX-compliant: Follow standard conventions
  8. Consider subcommands: For complex tools
  9. Test edge cases: Empty arguments, missing values, etc.
  10. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper