Command Line Parsing in C: Complete Guide

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 MethodProsConsBest For
Manual parsingSimple, no dependenciesError-prone, verboseSmall utilities
getopt()POSIX standard, reliableLimited to short optionsPortable programs
getopt_long()GNU style, long optionsGNU extensionMost Linux programs
argpVery powerful, built-in helpGNU specific, complexComplex GNU tools
SubcommandsGit-style interfaceMore complexMulti-purpose tools
Config filesPersistent settingsFile I/O overheadServer applications
Environment varsSystem-wide configHarder to discoverDaemon 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

PatternCode ExampleUse Case
Boolean flagcase 'v': verbose = true; break;Enable/disable features
Value optioncase 'p': port = atoi(optarg); break;Numeric configuration
String optioncase 'f': filename = optarg; break;File names, hostnames
Multiple valuescase 'I': includes[count++] = optarg;Lists of items
Key=valueparse_key_value(arg, &key, &value);Complex parameters
Subcommandsif (strcmp(argv[1], "add") == 0)Multi-tool programs
Config fileparse_config_file("app.conf", &cfg);Persistent settings

Conclusion

Command line parsing is a fundamental skill for C programmers:

Key Takeaways

  1. argc/argv provide access to command line arguments
  2. getopt() and getopt_long() simplify option parsing
  3. argp offers advanced features for complex programs
  4. Manual parsing works for simple cases
  5. Subcommands create intuitive multi-tool interfaces
  6. Configuration files complement command line options
  7. Environment variables provide system-wide settings
  8. 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.

Leave a Reply

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


Macro Nepal Helper