Introduction to Command Line Parsing
Command line argument parsing is essential for creating flexible and user-friendly C programs. Understanding how to process arguments passed to your program allows users to customize behavior, specify input/output files, and control program options.
Command Line Arguments Architecture
Program Execution: $ ./program arg1 arg2 arg3 Memory Layout: ┌─────────────────────┐ │ argv[0] = "./program"│ ├─────────────────────┤ │ argv[1] = "arg1" │ ├─────────────────────┤ │ argv[2] = "arg2" │ ├─────────────────────┤ │ argv[3] = "arg3" │ ├─────────────────────┤ │ argv[4] = NULL │ └─────────────────────┘ argc = 4 (number of arguments)
Basic Command Line Parsing
1. Simple Argument Access
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Number of arguments: %d\n", argc);
printf("Program name: %s\n", argv[0]);
if (argc > 1) {
printf("Arguments:\n");
for (int i = 1; i < argc; i++) {
printf(" argv[%d] = %s\n", i, argv[i]);
}
} else {
printf("No additional arguments provided.\n");
}
return 0;
}
Compile and run:
$ gcc args.c -o args $ ./args hello world 123 Number of arguments: 4 Program name: ./args Arguments: argv[1] = hello argv[2] = world argv[3] = 123
2. Using Pointer Arithmetic
#include <stdio.h>
int main(int argc, char **argv) {
printf("Arguments using pointer arithmetic:\n");
// argv is a pointer to an array of strings
for (int i = 0; i < argc; i++) {
printf(" argv[%d] = %s (at address %p)\n",
i, *(argv + i), (void*)*(argv + i));
}
// Alternative: using pointer directly
printf("\nUsing pointer directly:\n");
char **ptr = argv;
int count = 0;
while (*ptr != NULL) {
printf(" Argument %d: %s\n", count++, *ptr);
ptr++;
}
return 0;
}
Simple Option Parsing
3. Basic Flag Parsing
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
typedef struct {
bool verbose;
bool help;
int count;
char *input_file;
char *output_file;
} Options;
int main(int argc, char *argv[]) {
Options opts = {false, false, 0, NULL, NULL};
// Parse arguments
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], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
opts.help = true;
}
else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--count") == 0) {
if (i + 1 < argc) {
opts.count = atoi(argv[++i]);
} else {
printf("Error: --count requires a value\n");
return 1;
}
}
else if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--input") == 0) {
if (i + 1 < argc) {
opts.input_file = argv[++i];
} else {
printf("Error: --input requires a filename\n");
return 1;
}
}
else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) {
if (i + 1 < argc) {
opts.output_file = argv[++i];
} else {
printf("Error: --output requires a filename\n");
return 1;
}
}
else if (strcmp(argv[i], "--") == 0) {
// Stop processing options
break;
}
else if (argv[i][0] == '-') {
printf("Unknown option: %s\n", argv[i]);
return 1;
}
else {
printf("Unexpected argument: %s\n", argv[i]);
return 1;
}
}
// Display parsed options
printf("Parsed options:\n");
printf(" Verbose: %s\n", opts.verbose ? "yes" : "no");
printf(" Help: %s\n", opts.help ? "yes" : "no");
printf(" Count: %d\n", opts.count);
printf(" Input file: %s\n", opts.input_file ? opts.input_file : "(none)");
printf(" Output file: %s\n", opts.output_file ? opts.output_file : "(none)");
return 0;
}
Advanced Parsing with getopt
4. Using getopt() (POSIX)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
typedef struct {
int verbose;
int count;
char *input_file;
char *output_file;
int mode;
double threshold;
bool recursive;
char **remaining_args;
int remaining_count;
} ProgramOptions;
void print_usage(char *program_name) {
printf("Usage: %s [options] [files...]\n", program_name);
printf("Options:\n");
printf(" -v, --verbose Increase verbosity\n");
printf(" -c, --count NUM Set count value\n");
printf(" -i, --input FILE Input file\n");
printf(" -o, --output FILE Output file\n");
printf(" -m, --mode MODE Set mode (0-2)\n");
printf(" -t, --threshold VAL Set threshold (float)\n");
printf(" -r, --recursive Enable recursive processing\n");
printf(" -h, --help Show this help\n");
}
int main(int argc, char *argv[]) {
ProgramOptions opts = {0};
int opt;
// Long options structure
static struct option long_options[] = {
{"verbose", no_argument, 0, 'v'},
{"count", required_argument, 0, 'c'},
{"input", required_argument, 0, 'i'},
{"output", required_argument, 0, 'o'},
{"mode", required_argument, 0, 'm'},
{"threshold", required_argument, 0, 't'},
{"recursive", no_argument, 0, 'r'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
// Parse options
while ((opt = getopt_long(argc, argv, "vc:i:o:m:t:rh",
long_options, NULL)) != -1) {
switch (opt) {
case 'v':
opts.verbose++;
break;
case 'c':
opts.count = atoi(optarg);
break;
case 'i':
opts.input_file = optarg;
break;
case 'o':
opts.output_file = optarg;
break;
case 'm':
opts.mode = atoi(optarg);
if (opts.mode < 0 || opts.mode > 2) {
fprintf(stderr, "Mode must be 0, 1, or 2\n");
return 1;
}
break;
case 't':
opts.threshold = atof(optarg);
break;
case 'r':
opts.recursive = true;
break;
case 'h':
print_usage(argv[0]);
return 0;
case '?':
// getopt already printed an error message
return 1;
default:
fprintf(stderr, "Unexpected option: %c\n", opt);
return 1;
}
}
// Remaining arguments (non-option)
opts.remaining_args = &argv[optind];
opts.remaining_count = argc - optind;
// Display parsed options
printf("=== Parsed Options ===\n");
printf("Verbosity level: %d\n", opts.verbose);
printf("Count: %d\n", opts.count);
printf("Input file: %s\n", opts.input_file ? opts.input_file : "(none)");
printf("Output file: %s\n", opts.output_file ? opts.output_file : "(none)");
printf("Mode: %d\n", opts.mode);
printf("Threshold: %.2f\n", opts.threshold);
printf("Recursive: %s\n", opts.recursive ? "yes" : "no");
printf("\n=== Remaining Arguments ===\n");
printf("Number of remaining args: %d\n", opts.remaining_count);
for (int i = 0; i < opts.remaining_count; i++) {
printf(" arg[%d]: %s\n", i, opts.remaining_args[i]);
}
return 0;
}
Usage examples:
$ ./program -v -c 10 -i input.txt -o output.txt file1 file2 $ ./program --verbose --count=10 --input=input.txt file1 file2 $ ./program -v -m 2 -t 3.14 --recursive
Advanced getopt Examples
5. Complex getopt with Multiple Argument Types
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
typedef struct {
char *config_file;
char *log_file;
int port;
int threads;
int timeout;
char *hostname;
int debug_level;
int daemonize;
char **include_paths;
int include_count;
char **exclude_patterns;
int exclude_count;
} ServerConfig;
void print_config(ServerConfig *cfg) {
printf("Server Configuration:\n");
printf(" Config file: %s\n", cfg->config_file ? cfg->config_file : "(default)");
printf(" Log file: %s\n", cfg->log_file ? cfg->log_file : "(default)");
printf(" Port: %d\n", cfg->port);
printf(" Threads: %d\n", cfg->threads);
printf(" Timeout: %d seconds\n", cfg->timeout);
printf(" Hostname: %s\n", cfg->hostname ? cfg->hostname : "(default)");
printf(" Debug level: %d\n", cfg->debug_level);
printf(" Daemonize: %s\n", cfg->daemonize ? "yes" : "no");
if (cfg->include_count > 0) {
printf(" Include paths:\n");
for (int i = 0; i < cfg->include_count; i++) {
printf(" %s\n", cfg->include_paths[i]);
}
}
if (cfg->exclude_count > 0) {
printf(" Exclude patterns:\n");
for (int i = 0; i < cfg->exclude_count; i++) {
printf(" %s\n", cfg->exclude_patterns[i]);
}
}
}
// Function to parse integer with validation
int parse_int(const char *str, int min, int max, const char *name) {
char *endptr;
errno = 0;
long val = strtol(str, &endptr, 10);
if (errno != 0 || *endptr != '\0' || val < min || val > max) {
fprintf(stderr, "Invalid %s: must be integer between %d and %d\n",
name, min, max);
exit(1);
}
return (int)val;
}
int main(int argc, char *argv[]) {
ServerConfig cfg = {
.config_file = NULL,
.log_file = NULL,
.port = 8080, // default
.threads = 4, // default
.timeout = 30, // default
.hostname = NULL,
.debug_level = 0,
.daemonize = 0,
.include_paths = NULL,
.include_count = 0,
.exclude_patterns = NULL,
.exclude_count = 0
};
// Temporary storage for multi-value options
char *includes[100];
char *excludes[100];
static struct option long_options[] = {
{"config", required_argument, 0, 'c'},
{"log", required_argument, 0, 'l'},
{"port", required_argument, 0, 'p'},
{"threads", required_argument, 0, 't'},
{"timeout", required_argument, 0, 'T'},
{"host", required_argument, 0, 'H'},
{"debug", required_argument, 0, 'd'},
{"daemon", no_argument, 0, 'D'},
{"include", required_argument, 0, 'I'},
{"exclude", required_argument, 0, 'X'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "c:l:p:t:T:H:d:DI:X:h",
long_options, &option_index)) != -1) {
switch (opt) {
case 'c':
cfg.config_file = optarg;
break;
case 'l':
cfg.log_file = optarg;
break;
case 'p':
cfg.port = parse_int(optarg, 1, 65535, "port");
break;
case 't':
cfg.threads = parse_int(optarg, 1, 1000, "threads");
break;
case 'T':
cfg.timeout = parse_int(optarg, 1, 3600, "timeout");
break;
case 'H':
cfg.hostname = optarg;
break;
case 'd':
cfg.debug_level = parse_int(optarg, 0, 3, "debug level");
break;
case 'D':
cfg.daemonize = 1;
break;
case 'I':
if (cfg.include_count < 100) {
includes[cfg.include_count++] = optarg;
}
break;
case 'X':
if (cfg.exclude_count < 100) {
excludes[cfg.exclude_count++] = optarg;
}
break;
case 'h':
printf("Usage: %s [options]\n", argv[0]);
printf("Options:\n");
printf(" -c, --config FILE Configuration file\n");
printf(" -l, --log FILE Log file\n");
printf(" -p, --port PORT Port number (1-65535)\n");
printf(" -t, --threads NUM Number of threads\n");
printf(" -T, --timeout SEC Timeout in seconds\n");
printf(" -H, --host HOST Hostname\n");
printf(" -d, --debug LEVEL Debug level (0-3)\n");
printf(" -D, --daemon Run as daemon\n");
printf(" -I, --include PATH Include path (can be repeated)\n");
printf(" -X, --exclude PATTERN Exclude pattern (can be repeated)\n");
printf(" -h, --help Show this help\n");
return 0;
case '?':
return 1;
}
}
// Copy collected arrays to config
if (cfg.include_count > 0) {
cfg.include_paths = includes;
}
if (cfg.exclude_count > 0) {
cfg.exclude_patterns = excludes;
}
print_config(&cfg);
return 0;
}
Manual Parsing Techniques
6. Manual Option Parsing
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct {
bool verbose;
bool quiet;
int level;
char *input;
char *output;
int width;
int height;
char *format;
bool overwrite;
} ImageOptions;
// Helper function to check if string starts with prefix
bool starts_with(const char *str, const char *prefix) {
return strncmp(str, prefix, strlen(prefix)) == 0;
}
// Helper function to parse key=value pairs
bool parse_key_value(const char *arg, char **key, char **value) {
char *equals = strchr(arg, '=');
if (!equals) return false;
size_t key_len = equals - arg;
*key = malloc(key_len + 1);
strncpy(*key, arg, key_len);
(*key)[key_len] = '\0';
*value = strdup(equals + 1);
return true;
}
int main(int argc, char *argv[]) {
ImageOptions opts = {
.verbose = false,
.quiet = false,
.level = 5,
.input = NULL,
.output = NULL,
.width = 800,
.height = 600,
.format = "png",
.overwrite = false
};
// Manual parsing
for (int i = 1; i < argc; i++) {
char *arg = argv[i];
// Boolean flags
if (strcmp(arg, "-v") == 0 || strcmp(arg, "--verbose") == 0) {
opts.verbose = true;
}
else if (strcmp(arg, "-q") == 0 || strcmp(arg, "--quiet") == 0) {
opts.quiet = true;
}
else if (strcmp(arg, "-f") == 0 || strcmp(arg, "--force") == 0) {
opts.overwrite = true;
}
// Options with arguments
else if (strcmp(arg, "-l") == 0 || strcmp(arg, "--level") == 0) {
if (i + 1 < argc) {
opts.level = atoi(argv[++i]);
} else {
fprintf(stderr, "Error: --level requires a value\n");
return 1;
}
}
else if (strcmp(arg, "-i") == 0 || strcmp(arg, "--input") == 0) {
if (i + 1 < argc) {
opts.input = argv[++i];
} else {
fprintf(stderr, "Error: --input requires a filename\n");
return 1;
}
}
else if (strcmp(arg, "-o") == 0 || strcmp(arg, "--output") == 0) {
if (i + 1 < argc) {
opts.output = argv[++i];
} else {
fprintf(stderr, "Error: --output requires a filename\n");
return 1;
}
}
else if (strcmp(arg, "--size") == 0) {
if (i + 2 < argc) {
opts.width = atoi(argv[++i]);
opts.height = atoi(argv[++i]);
} else {
fprintf(stderr, "Error: --size requires width and height\n");
return 1;
}
}
// Key=value format
else if (starts_with(arg, "--format=")) {
opts.format = strchr(arg, '=') + 1;
}
else if (starts_with(arg, "--width=")) {
opts.width = atoi(strchr(arg, '=') + 1);
}
else if (starts_with(arg, "--height=")) {
opts.height = atoi(strchr(arg, '=') + 1);
}
// Unknown option
else if (arg[0] == '-') {
fprintf(stderr, "Unknown option: %s\n", arg);
return 1;
}
// Positional arguments
else {
printf("Positional argument: %s\n", arg);
}
}
// Display configuration
printf("Image Options:\n");
printf(" Verbose: %s\n", opts.verbose ? "yes" : "no");
printf(" Quiet: %s\n", opts.quiet ? "yes" : "no");
printf(" Level: %d\n", opts.level);
printf(" Input: %s\n", opts.input ? opts.input : "(none)");
printf(" Output: %s\n", opts.output ? opts.output : "(none)");
printf(" Size: %d x %d\n", opts.width, opts.height);
printf(" Format: %s\n", opts.format);
printf(" Overwrite: %s\n", opts.overwrite ? "yes" : "no");
return 0;
}
Using argp (GNU argp parser)
7. GNU argp Parser
#include <stdio.h> #include <stdlib.h> #include <argp.h> const char *argp_program_version = "program 1.0"; const char *argp_program_bug_address = "<[email protected]>"; /* Program documentation. */ static char doc[] = "Program to demonstrate GNU argp parsing"; /* A description of the arguments we accept. */ static char args_doc[] = "INPUT_FILE OUTPUT_FILE"; /* The options we understand. */ static struct argp_option options[] = { {"verbose", 'v', 0, 0, "Produce verbose output"}, {"quiet", 'q', 0, 0, "Don't produce any output"}, {"silent", 's', 0, OPTION_ALIAS}, {"output", 'o', "FILE", 0, "Output to FILE instead of standard output"}, {"count", 'c', "NUM", 0, "Process NUM times"}, {"mode", 'm', "MODE", 0, "Processing mode (0,1,2)"}, {0} }; /* Used by main to communicate with parse_opt. */ struct arguments { char *args[2]; /* INPUT_FILE, OUTPUT_FILE */ int silent, verbose; char *output_file; int count; int mode; }; /* 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 'q': case 's': arguments->silent = 1; break; case 'o': arguments->output_file = arg; break; case 'c': arguments->count = atoi(arg); break; case 'm': arguments->mode = atoi(arg); break; case ARGP_KEY_ARG: if (state->arg_num >= 2) { /* Too many arguments. */ argp_usage(state); } arguments->args[state->arg_num] = arg; break; case ARGP_KEY_END: if (state->arg_num < 2) { /* Not enough arguments. */ argp_usage(state); } break; default: return ARGP_ERR_UNKNOWN; } return 0; } /* Our argp parser. */ static struct argp argp = {options, parse_opt, args_doc, doc}; int main(int argc, char *argv[]) { struct arguments arguments; /* Default values. */ arguments.silent = 0; arguments.verbose = 0; arguments.output_file = "-"; arguments.count = 1; arguments.mode = 0; /* Parse our arguments. */ argp_parse(&argp, argc, argv, 0, 0, &arguments); /* Display results. */ printf("INPUT_FILE = %s\n", arguments.args[0]); printf("OUTPUT_FILE = %s\n", arguments.args[1]); printf("VERBOSE = %s\n", arguments.verbose ? "yes" : "no"); printf("SILENT = %s\n", arguments.silent ? "yes" : "no"); printf("OUTPUT_FILE = %s\n", arguments.output_file); printf("COUNT = %d\n", arguments.count); printf("MODE = %d\n", arguments.mode); return 0; }
Compile and run:
$ gcc -o argp_example argp_example.c $ ./argp_example -v -c 5 -m 2 input.txt output.txt
Subcommand Parsing
8. Git-style Subcommands
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct {
char *command;
int argc;
char **argv;
} Subcommand;
// Subcommand handlers
int cmd_add(int argc, char *argv[]) {
printf("Executing 'add' command\n");
for (int i = 0; i < argc; i++) {
printf(" add arg[%d]: %s\n", i, argv[i]);
}
return 0;
}
int cmd_commit(int argc, char *argv[]) {
char *message = NULL;
int amend = 0;
printf("Executing 'commit' command\n");
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "-m") == 0 && i + 1 < argc) {
message = argv[++i];
} else if (strcmp(argv[i], "--amend") == 0) {
amend = 1;
} else {
printf(" Unknown commit option: %s\n", argv[i]);
}
}
if (message) {
printf(" Message: %s\n", message);
}
printf(" Amend: %s\n", amend ? "yes" : "no");
return 0;
}
int cmd_push(int argc, char *argv[]) {
char *remote = NULL;
char *branch = NULL;
int force = 0;
printf("Executing 'push' command\n");
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--force") == 0) {
force = 1;
} else if (remote == NULL) {
remote = argv[i];
} else if (branch == NULL) {
branch = argv[i];
}
}
printf(" Remote: %s\n", remote ? remote : "(default)");
printf(" Branch: %s\n", branch ? branch : "(default)");
printf(" Force: %s\n", force ? "yes" : "no");
return 0;
}
int cmd_status(int argc, char *argv[]) {
int short_format = 0;
int branch_info = 0;
printf("Executing 'status' command\n");
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--short") == 0) {
short_format = 1;
} else if (strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--branch") == 0) {
branch_info = 1;
}
}
printf(" Short format: %s\n", short_format ? "yes" : "no");
printf(" Branch info: %s\n", branch_info ? "yes" : "no");
return 0;
}
void print_usage(char *progname) {
printf("Usage: %s <command> [options]\n", progname);
printf("\nCommands:\n");
printf(" add <files...> Add file contents to the index\n");
printf(" commit -m MSG Record changes to the repository\n");
printf(" push [remote] [branch] Update remote refs\n");
printf(" status Show the working tree status\n");
printf("\nGlobal options:\n");
printf(" -h, --help Show this help\n");
}
int main(int argc, char *argv[]) {
if (argc < 2) {
print_usage(argv[0]);
return 1;
}
// Check for global help
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
// Parse command
char *command = argv[1];
int cmd_argc = argc - 2;
char **cmd_argv = (cmd_argc > 0) ? &argv[2] : NULL;
// Execute command
if (strcmp(command, "add") == 0) {
return cmd_add(cmd_argc, cmd_argv);
} else if (strcmp(command, "commit") == 0) {
return cmd_commit(cmd_argc, cmd_argv);
} else if (strcmp(command, "push") == 0) {
return cmd_push(cmd_argc, cmd_argv);
} else if (strcmp(command, "status") == 0) {
return cmd_status(cmd_argc, cmd_argv);
} else {
fprintf(stderr, "Unknown command: %s\n", command);
print_usage(argv[0]);
return 1;
}
}
Usage examples:
$ ./git add file1.c file2.c $ ./git commit -m "Initial commit" $ ./git push origin main $ ./git status -s
Environment Variables
9. Environment Variable Parsing
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *home;
char *user;
char *path;
int debug;
int threads;
char *config_dir;
} EnvironmentConfig;
// Get environment variable with default
char* get_env_default(const char *name, const char *default_value) {
char *value = getenv(name);
return value ? value : (char*)default_value;
}
// Parse integer from environment
int get_env_int(const char *name, int default_value) {
char *value = getenv(name);
if (value) {
return atoi(value);
}
return default_value;
}
int main(int argc, char *argv[]) {
EnvironmentConfig cfg;
// Get standard environment variables
cfg.home = get_env_default("HOME", "/tmp");
cfg.user = get_env_default("USER", "unknown");
cfg.path = get_env_default("PATH", "");
// Application-specific environment variables
cfg.debug = get_env_int("MYAPP_DEBUG", 0);
cfg.threads = get_env_int("MYAPP_THREADS", 4);
cfg.config_dir = get_env_default("MYAPP_CONFIG", "/etc/myapp");
printf("=== Environment Configuration ===\n");
printf("HOME: %s\n", cfg.home);
printf("USER: %s\n", cfg.user);
printf("PATH: %s\n", cfg.path);
printf("MYAPP_DEBUG: %d\n", cfg.debug);
printf("MYAPP_THREADS: %d\n", cfg.threads);
printf("MYAPP_CONFIG: %s\n", cfg.config_dir);
// Override with command line arguments
printf("\n=== Command Line Overrides ===\n");
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--debug") == 0 && i + 1 < argc) {
cfg.debug = atoi(argv[++i]);
printf("Debug overridden to: %d\n", cfg.debug);
} else if (strcmp(argv[i], "--threads") == 0 && i + 1 < argc) {
cfg.threads = atoi(argv[++i]);
printf("Threads overridden to: %d\n", cfg.threads);
}
}
return 0;
}
Usage:
$ export MYAPP_DEBUG=1 $ export MYAPP_THREADS=8 $ ./program --debug 2 --threads 16
Configuration File Support
10. Combined Command Line and Config File
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_LINE 256
typedef struct {
char *server;
int port;
char *database;
char *username;
char *password;
int max_connections;
int timeout;
int debug;
} DatabaseConfig;
// Trim whitespace from string
char* trim(char *str) {
char *end;
// Trim leading space
while (isspace((unsigned char)*str)) str++;
if (*str == 0) return str;
// Trim trailing space
end = str + strlen(str) - 1;
while (end > str && isspace((unsigned char)*end)) end--;
// Write new null terminator
*(end + 1) = '\0';
return str;
}
// Parse config file
void parse_config_file(const char *filename, DatabaseConfig *cfg) {
FILE *file = fopen(filename, "r");
if (!file) {
printf("No config file found, using defaults\n");
return;
}
char line[MAX_LINE];
int line_num = 0;
while (fgets(line, sizeof(line), file)) {
line_num++;
// Remove comments
char *comment = strchr(line, '#');
if (comment) *comment = '\0';
// Trim whitespace
char *trimmed = trim(line);
if (strlen(trimmed) == 0) continue;
// Split key=value
char *equals = strchr(trimmed, '=');
if (!equals) {
printf("Warning: line %d: no '=' found\n", line_num);
continue;
}
*equals = '\0';
char *key = trim(trimmed);
char *value = trim(equals + 1);
// Parse key-value pairs
if (strcmp(key, "server") == 0) {
cfg->server = strdup(value);
} else if (strcmp(key, "port") == 0) {
cfg->port = atoi(value);
} else if (strcmp(key, "database") == 0) {
cfg->database = strdup(value);
} else if (strcmp(key, "username") == 0) {
cfg->username = strdup(value);
} else if (strcmp(key, "password") == 0) {
cfg->password = strdup(value);
} else if (strcmp(key, "max_connections") == 0) {
cfg->max_connections = atoi(value);
} else if (strcmp(key, "timeout") == 0) {
cfg->timeout = atoi(value);
} else if (strcmp(key, "debug") == 0) {
cfg->debug = atoi(value);
}
}
fclose(file);
}
// Parse command line arguments (overrides config file)
void parse_command_line(int argc, char *argv[], DatabaseConfig *cfg) {
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) {
if (i + 1 < argc) {
parse_config_file(argv[++i], cfg);
}
} else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--server") == 0) {
if (i + 1 < argc) cfg->server = argv[++i];
} else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) {
if (i + 1 < argc) cfg->port = atoi(argv[++i]);
} else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--database") == 0) {
if (i + 1 < argc) cfg->database = argv[++i];
} else if (strcmp(argv[i], "-u") == 0 || strcmp(argv[i], "--user") == 0) {
if (i + 1 < argc) cfg->username = argv[++i];
} else if (strcmp(argv[i], "-P") == 0 || strcmp(argv[i], "--password") == 0) {
if (i + 1 < argc) cfg->password = argv[++i];
} else if (strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--max-conn") == 0) {
if (i + 1 < argc) cfg->max_connections = atoi(argv[++i]);
} else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--timeout") == 0) {
if (i + 1 < argc) cfg->timeout = atoi(argv[++i]);
} else if (strcmp(argv[i], "--debug") == 0) {
cfg->debug = 1;
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
printf("Usage: %s [options]\n", argv[0]);
printf("Options:\n");
printf(" -c, --config FILE Config file\n");
printf(" -s, --server HOST Database server\n");
printf(" -p, --port PORT Server port\n");
printf(" -d, --database DB Database name\n");
printf(" -u, --user USER Username\n");
printf(" -P, --password PASS Password\n");
printf(" -m, --max-conn NUM Max connections\n");
printf(" -t, --timeout SEC Timeout\n");
printf(" --debug Enable debug\n");
exit(0);
}
}
}
int main(int argc, char *argv[]) {
DatabaseConfig cfg = {
.server = "localhost",
.port = 5432,
.database = "mydb",
.username = NULL,
.password = NULL,
.max_connections = 100,
.timeout = 30,
.debug = 0
};
// Parse command line (which may specify config file)
parse_command_line(argc, argv, &cfg);
// Display configuration
printf("=== Database Configuration ===\n");
printf("Server: %s\n", cfg.server);
printf("Port: %d\n", cfg.port);
printf("Database: %s\n", cfg.database);
printf("Username: %s\n", cfg.username ? cfg.username : "(none)");
printf("Password: %s\n", cfg.password ? "********" : "(none)");
printf("Max connections: %d\n", cfg.max_connections);
printf("Timeout: %d seconds\n", cfg.timeout);
printf("Debug: %s\n", cfg.debug ? "yes" : "no");
return 0;
}
Example config file (database.conf):
# Database configuration server = db.example.com port = 3306 database = production_db username = app_user password = secret123 max_connections = 200 timeout = 60 debug = 1
Argument Validation and Error Handling
11. Robust Argument Validation
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
typedef struct {
char *input_file;
char *output_file;
int width;
int height;
int quality;
char *format;
int verbose;
} ImageConfig;
// Validate file extension
int valid_extension(const char *filename, const char *ext) {
const char *dot = strrchr(filename, '.');
return dot && strcasecmp(dot + 1, ext) == 0;
}
// Validate positive integer
int validate_positive_int(const char *str, const char *name, int min, int max) {
char *endptr;
errno = 0;
long val = strtol(str, &endptr, 10);
if (errno != 0 || *endptr != '\0') {
fprintf(stderr, "Error: %s must be an integer\n", name);
exit(1);
}
if (val < min || val > max) {
fprintf(stderr, "Error: %s must be between %d and %d\n", name, min, max);
exit(1);
}
return (int)val;
}
// Validate format string
int validate_format(const char *format) {
static const char *valid_formats[] = {"png", "jpg", "jpeg", "bmp", "gif", "tiff"};
int num_formats = sizeof(valid_formats) / sizeof(valid_formats[0]);
for (int i = 0; i < num_formats; i++) {
if (strcasecmp(format, valid_formats[i]) == 0) {
return 1;
}
}
return 0;
}
// Validate input file exists
int file_exists(const char *filename) {
FILE *file = fopen(filename, "r");
if (file) {
fclose(file);
return 1;
}
return 0;
}
int main(int argc, char *argv[]) {
ImageConfig cfg = {
.input_file = NULL,
.output_file = NULL,
.width = 800,
.height = 600,
.quality = 90,
.format = "png",
.verbose = 0
};
// Parse arguments with validation
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--input") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: --input requires a filename\n");
return 1;
}
cfg.input_file = argv[++i];
// Validate input file exists
if (!file_exists(cfg.input_file)) {
fprintf(stderr, "Error: input file '%s' does not exist\n", cfg.input_file);
return 1;
}
}
else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: --output requires a filename\n");
return 1;
}
cfg.output_file = argv[++i];
}
else if (strcmp(argv[i], "--width") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: --width requires a value\n");
return 1;
}
cfg.width = validate_positive_int(argv[++i], "width", 1, 10000);
}
else if (strcmp(argv[i], "--height") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: --height requires a value\n");
return 1;
}
cfg.height = validate_positive_int(argv[++i], "height", 1, 10000);
}
else if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quality") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: --quality requires a value\n");
return 1;
}
cfg.quality = validate_positive_int(argv[++i], "quality", 1, 100);
}
else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--format") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: --format requires a value\n");
return 1;
}
cfg.format = argv[++i];
// Validate format
if (!validate_format(cfg.format)) {
fprintf(stderr, "Error: invalid format '%s'. Supported: png, jpg, bmp, gif, tiff\n",
cfg.format);
return 1;
}
}
else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
cfg.verbose = 1;
}
else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
printf("Usage: %s [options]\n", argv[0]);
printf("Options:\n");
printf(" -i, --input FILE Input image file (required)\n");
printf(" -o, --output FILE Output image file (required)\n");
printf(" --width WIDTH Output width (1-10000)\n");
printf(" --height HEIGHT Output height (1-10000)\n");
printf(" -q, --quality Q JPEG quality (1-100)\n");
printf(" -f, --format FMT Output format (png, jpg, bmp, gif, tiff)\n");
printf(" -v, --verbose Verbose output\n");
printf(" -h, --help Show this help\n");
return 0;
}
else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
return 1;
}
}
// Validate required arguments
if (!cfg.input_file) {
fprintf(stderr, "Error: input file is required\n");
return 1;
}
if (!cfg.output_file) {
fprintf(stderr, "Error: output file is required\n");
return 1;
}
// Validate output file extension matches format
if (!valid_extension(cfg.output_file, cfg.format)) {
fprintf(stderr, "Warning: output file extension does not match format '%s'\n", cfg.format);
}
// Display configuration
printf("=== Image Processing Configuration ===\n");
printf("Input: %s\n", cfg.input_file);
printf("Output: %s\n", cfg.output_file);
printf("Size: %d x %d\n", cfg.width, cfg.height);
printf("Quality: %d\n", cfg.quality);
printf("Format: %s\n", cfg.format);
printf("Verbose: %s\n", cfg.verbose ? "yes" : "no");
return 0;
}
Summary Table
| Parsing Method | Pros | Cons | Best For |
|---|---|---|---|
| Manual parsing | Simple, no dependencies | Error-prone, verbose | Small utilities |
| getopt() | POSIX standard, reliable | Limited to short options | Portable programs |
| getopt_long() | GNU style, long options | GNU extension | Most Linux programs |
| argp | Very powerful, built-in help | GNU specific, complex | Complex GNU tools |
| Subcommands | Git-style interface | More complex | Multi-purpose tools |
| Config files | Persistent settings | File I/O overhead | Server applications |
| Environment vars | System-wide config | Harder to discover | Daemon configuration |
Best Practices Summary
Do's and Don'ts
// DO: Validate all user input
if (argc < 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return 1;
}
// DO: Use consistent option naming
// Short: -v, -f, -o
// Long: --verbose, --file, --output
// DO: Provide meaningful help text
void print_help() {
printf("Usage: %s [OPTIONS] <file>\n", program_name);
printf("Options:\n");
printf(" -v, --verbose Increase verbosity\n");
}
// DO: Check for required arguments
if (!input_file) {
fprintf(stderr, "Error: input file required\n");
return 1;
}
// DON'T: Ignore unused arguments
while ((opt = getopt(argc, argv, "vf:o:")) != -1) {
// Handle all options
}
// DON'T: Use ambiguous option names
// Bad: -a (what does it do?)
// Good: -a, --append
// DO: Handle the -- separator
if (strcmp(argv[i], "--") == 0) {
// Stop processing options
i++;
break;
}
// DO: Validate numeric ranges
int port = atoi(optarg);
if (port < 1 || port > 65535) {
fprintf(stderr, "Invalid port number\n");
return 1;
}
Common Patterns Summary
| Pattern | Code Example | Use Case |
|---|---|---|
| Boolean flag | case 'v': verbose = true; break; | Enable/disable features |
| Value option | case 'p': port = atoi(optarg); break; | Numeric configuration |
| String option | case 'f': filename = optarg; break; | File names, hostnames |
| Multiple values | case 'I': includes[count++] = optarg; | Lists of items |
| Key=value | parse_key_value(arg, &key, &value); | Complex parameters |
| Subcommands | if (strcmp(argv[1], "add") == 0) | Multi-tool programs |
| Config file | parse_config_file("app.conf", &cfg); | Persistent settings |
Conclusion
Command line parsing is a fundamental skill for C programmers:
Key Takeaways
- argc/argv provide access to command line arguments
- getopt() and getopt_long() simplify option parsing
- argp offers advanced features for complex programs
- Manual parsing works for simple cases
- Subcommands create intuitive multi-tool interfaces
- Configuration files complement command line options
- Environment variables provide system-wide settings
- Validation is crucial for robust programs
Choosing the Right Approach
- Simple scripts: Manual parsing or basic getopt
- Standard Unix tools: getopt() for POSIX compliance
- GNU/Linux programs: getopt_long() for long options
- Complex tools: argp for built-in help and features
- Git-like tools: Subcommand architecture
- Servers/daemons: Config files + command line overrides
Mastering command line parsing enables you to create professional, user-friendly programs that follow established conventions and provide flexible configuration options.