Command line arguments are a fundamental feature of C programming that allow users to pass information to programs at startup. They transform static executables into dynamic, configurable tools that can adapt to different inputs without recompilation. For C developers, mastering command line argument handling is essential for creating professional, user-friendly applications that integrate seamlessly with scripts, build systems, and other command-line tools.
What are Command Line Arguments?
Command line arguments are parameters supplied to a program when it is executed from a command-line interface (CLI) or shell. In C, these arguments are passed to the main() function through two parameters: argc (argument count) and argv (argument vector). This mechanism enables programs to receive input, configuration options, and file names directly from the user or from other programs.
Why Command Line Arguments are Essential in C
- Program Flexibility: One executable can perform different operations based on arguments.
- Scripting Integration: Enables programs to be used in shell scripts and automation pipelines.
- User Control: Users can customize program behavior without modifying source code.
- Toolchain Compatibility: Standard behavior expected by Unix/Linux tools and developers.
- Configuration Management: Pass configuration parameters at runtime instead of hardcoding.
The argc and argv Parameters
#include <stdio.h>
// Basic command line argument demonstration
int main(int argc, char *argv[]) {
printf("=== Command Line Arguments ===\n");
printf("Number of arguments (argc): %d\n\n", argc);
printf("Arguments received:\n");
for (int i = 0; i < argc; i++) {
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}
Compile and run:
gcc -o args args.c ./args hello world 42 "multi word argument"
Output:
=== Command Line Arguments === Number of arguments (argc): 5 Arguments received: argv[0]: ./args argv[1]: hello argv[2]: world argv[3]: 42 argv[4]: multi word argument
Understanding argc and argv
#include <stdio.h>
// Detailed explanation of argc and argv
int main(int argc, char *argv[]) {
printf("=== Understanding argc and argv ===\n\n");
// argc is the count of arguments
printf("argc = %d\n", argc);
printf("This means %d argument%s were passed.\n\n",
argc, argc == 1 ? "" : "s");
// argv[0] is always the program name
printf("argv[0] = \"%s\" (program name/execution path)\n", argv[0]);
// argv[argc] is guaranteed to be NULL
printf("argv[%d] = %s (NULL pointer)\n\n", argc, argv[argc] ? "not NULL" : "NULL");
// Demonstrate that argv is an array of strings
printf("All arguments:\n");
for (int i = 0; i < argc; i++) {
printf(" argv[%d] (address %p): \"%s\"\n",
i, (void*)argv[i], argv[i]);
}
// Show memory layout
printf("\nMemory layout:\n");
printf(" argv array starts at: %p\n", (void*)argv);
for (int i = 0; i <= argc; i++) {
printf(" argv[%d] at %p contains %p\n",
i, (void*)&argv[i], (void*)argv[i]);
}
return 0;
}
Alternative main() Signatures
#include <stdio.h>
// Standard signatures for main()
int main() {
// No arguments - program cannot access command line
printf("No argument version - argc/argv not available\n");
return 0;
}
/*
// Most common signature
int main(int argc, char *argv[]) {
// argc = argument count, argv = argument values
}
// Equivalent signature (char **argv)
int main(int argc, char **argv) {
// argv is a pointer to pointer to char
}
// Environment variables also accessible
int main(int argc, char *argv[], char *envp[]) {
// envp contains environment variables
}
*/
// Example with environment variables
int main(int argc, char *argv[], char *envp[]) {
printf("=== Environment Variables ===\n");
printf("argc = %d\n", argc);
printf("\nFirst few environment variables:\n");
for (int i = 0; i < 5 && envp[i] != NULL; i++) {
printf("envp[%d]: %s\n", i, envp[i]);
}
return 0;
}
Parsing Numeric Arguments
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
// Function to safely convert string to integer
int safeStringToInt(const char *str, int *result) {
char *endptr;
errno = 0; // Reset errno
long val = strtol(str, &endptr, 10);
// Check for various conversion errors
if (errno == ERANGE || val > INT_MAX || val < INT_MIN) {
return -1; // Range error
}
if (endptr == str) {
return -2; // No digits found
}
if (*endptr != '\0') {
return -3; // Trailing characters
}
*result = (int)val;
return 0; // Success
}
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <number1> <number2> [number3 ...]\n", argv[0]);
return 1;
}
printf("=== Numeric Arguments ===\n");
int sum = 0;
int count = 0;
for (int i = 1; i < argc; i++) {
int value;
int status = safeStringToInt(argv[i], &value);
if (status == 0) {
printf("argv[%d]: %s = %d\n", i, argv[i], value);
sum += value;
count++;
} else {
printf("argv[%d]: %s - INVALID (", i, argv[i]);
switch (status) {
case -1: printf("out of range"); break;
case -2: printf("not a number"); break;
case -3: printf("extra characters"); break;
}
printf(")\n");
}
}
if (count > 0) {
printf("\nSum = %d\n", sum);
printf("Average = %.2f\n", (float)sum / count);
}
return 0;
}
Parsing Floating-Point Arguments
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
double safeStringToDouble(const char *str, int *success) {
char *endptr;
errno = 0;
double val = strtod(str, &endptr);
*success = 0; // Assume failure
if (errno == ERANGE) {
return 0.0; // Range error
}
if (endptr == str) {
return 0.0; // No conversion
}
if (*endptr != '\0') {
return 0.0; // Trailing characters
}
*success = 1; // Success
return val;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <float1> <float2> ...\n", argv[0]);
return 1;
}
printf("=== Floating Point Arguments ===\n");
double values[argc - 1];
int validCount = 0;
for (int i = 1; i < argc; i++) {
int success;
double val = safeStringToDouble(argv[i], &success);
if (success) {
values[validCount++] = val;
printf("argv[%d]: %s = %f\n", i, argv[i], val);
} else {
printf("argv[%d]: %s - INVALID\n", i, argv[i]);
}
}
if (validCount > 0) {
double sum = 0, min = values[0], max = values[0];
for (int i = 0; i < validCount; i++) {
sum += values[i];
if (values[i] < min) min = values[i];
if (values[i] > max) max = values[i];
}
printf("\nStatistics:\n");
printf(" Count: %d\n", validCount);
printf(" Sum: %f\n", sum);
printf(" Average: %f\n", sum / validCount);
printf(" Min: %f\n", min);
printf(" Max: %f\n", max);
}
return 0;
}
Simple Argument Parsing (Positional)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// Simple file copy program using positional arguments
int main(int argc, char *argv[]) {
// Check minimum arguments
if (argc < 3) {
fprintf(stderr, "Error: Insufficient arguments\n");
fprintf(stderr, "Usage: %s <source_file> <destination_file> [buffer_size]\n",
argv[0]);
fprintf(stderr, " buffer_size: optional (default 4096 bytes)\n");
return 1;
}
// Parse arguments
char *sourceFile = argv[1];
char *destFile = argv[2];
int bufferSize = 4096; // Default
// Optional third argument
if (argc >= 4) {
char *endptr;
long size = strtol(argv[3], &endptr, 10);
if (*endptr != '\0' || size <= 0) {
fprintf(stderr, "Error: Invalid buffer size '%s'\n", argv[3]);
return 1;
}
bufferSize = (int)size;
}
printf("=== File Copy ===\n");
printf("Source: %s\n", sourceFile);
printf("Destination: %s\n", destFile);
printf("Buffer Size: %d bytes\n", bufferSize);
// Perform file copy (simplified - error checking omitted)
FILE *src = fopen(sourceFile, "rb");
FILE *dst = fopen(destFile, "wb");
if (!src || !dst) {
fprintf(stderr, "Error: Cannot open files\n");
if (src) fclose(src);
if (dst) fclose(dst);
return 1;
}
char *buffer = malloc(bufferSize);
size_t bytesRead;
while ((bytesRead = fread(buffer, 1, bufferSize, src)) > 0) {
fwrite(buffer, 1, bytesRead, dst);
}
printf("Copy completed successfully\n");
free(buffer);
fclose(src);
fclose(dst);
return 0;
}
Flag-Based Argument Parsing
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct {
bool verbose;
bool recursive;
bool force;
int timeout;
char *inputFile;
char *outputFile;
} Config;
void printUsage(const char *programName) {
fprintf(stderr, "Usage: %s [OPTIONS] <input_file> [output_file]\n", programName);
fprintf(stderr, "Options:\n");
fprintf(stderr, " -v, --verbose Enable verbose output\n");
fprintf(stderr, " -r, --recursive Process directories recursively\n");
fprintf(stderr, " -f, --force Force overwrite\n");
fprintf(stderr, " -t, --timeout N Set timeout in seconds (default: 30)\n");
fprintf(stderr, " -h, --help Show this help message\n");
}
int parseArguments(int argc, char *argv[], Config *config) {
// Initialize defaults
config->verbose = false;
config->recursive = false;
config->force = false;
config->timeout = 30;
config->inputFile = NULL;
config->outputFile = NULL;
int i = 1; // Start after program name
while (i < argc) {
// Help option
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
printUsage(argv[0]);
exit(0);
}
// Verbose option
else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
config->verbose = true;
i++;
}
// Recursive option
else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--recursive") == 0) {
config->recursive = true;
i++;
}
// Force option
else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--force") == 0) {
config->force = true;
i++;
}
// Timeout option (requires value)
else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--timeout") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: --timeout requires a value\n");
return -1;
}
char *endptr;
long val = strtol(argv[i + 1], &endptr, 10);
if (*endptr != '\0' || val <= 0) {
fprintf(stderr, "Error: Invalid timeout value '%s'\n", argv[i + 1]);
return -1;
}
config->timeout = (int)val;
i += 2; // Consume option and value
}
// Input file (first non-option argument)
else if (config->inputFile == NULL && argv[i][0] != '-') {
config->inputFile = argv[i];
i++;
}
// Output file (second non-option argument)
else if (config->outputFile == NULL && argv[i][0] != '-') {
config->outputFile = argv[i];
i++;
}
// Unknown option
else if (argv[i][0] == '-') {
fprintf(stderr, "Error: Unknown option '%s'\n", argv[i]);
return -1;
}
// Extra argument
else {
fprintf(stderr, "Error: Unexpected argument '%s'\n", argv[i]);
return -1;
}
}
// Validate required arguments
if (config->inputFile == NULL) {
fprintf(stderr, "Error: Input file required\n");
return -1;
}
return 0; // Success
}
int main(int argc, char *argv[]) {
Config config;
if (parseArguments(argc, argv, &config) != 0) {
printUsage(argv[0]);
return 1;
}
printf("=== Configuration ===\n");
printf("Verbose: %s\n", config.verbose ? "yes" : "no");
printf("Recursive: %s\n", config.recursive ? "yes" : "no");
printf("Force: %s\n", config.force ? "yes" : "no");
printf("Timeout: %d seconds\n", config.timeout);
printf("Input file: %s\n", config.inputFile);
printf("Output file: %s\n", config.outputFile ? config.outputFile : "(none)");
// Rest of program logic...
return 0;
}
Using getopt() for POSIX Argument Parsing
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
// POSIX getopt() example
int main(int argc, char *argv[]) {
int verbose = 0;
int recursive = 0;
int timeout = 30;
char *inputFile = NULL;
char *outputFile = NULL;
int opt;
// getopt() options: short options
while ((opt = getopt(argc, argv, "vrf:t:h")) != -1) {
switch (opt) {
case 'v':
verbose = 1;
break;
case 'r':
recursive = 1;
break;
case 'f':
force = 1;
break;
case 't':
timeout = atoi(optarg);
break;
case 'h':
fprintf(stderr, "Usage: %s [-v] [-r] [-f] [-t timeout] input [output]\n",
argv[0]);
return 0;
case '?':
fprintf(stderr, "Unknown option\n");
return 1;
}
}
// Remaining arguments are positional
if (optind < argc) {
inputFile = argv[optind++];
}
if (optind < argc) {
outputFile = argv[optind++];
}
if (optind < argc) {
fprintf(stderr, "Error: Extra arguments\n");
return 1;
}
if (inputFile == NULL) {
fprintf(stderr, "Error: Input file required\n");
return 1;
}
printf("=== getopt() Parsing ===\n");
printf("Verbose: %d\n", verbose);
printf("Recursive: %d\n", recursive);
printf("Force: %d\n", force);
printf("Timeout: %d\n", timeout);
printf("Input: %s\n", inputFile);
printf("Output: %s\n", outputFile ? outputFile : "(none)");
return 0;
}
Advanced getopt_long() for GNU-Style Options
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
int main(int argc, char *argv[]) {
int verbose = 0;
int recursive = 0;
int force = 0;
int timeout = 30;
char *inputFile = NULL;
char *outputFile = NULL;
// Define long options
static struct option long_options[] = {
{"verbose", no_argument, 0, 'v'},
{"recursive", no_argument, 0, 'r'},
{"force", no_argument, 0, 'f'},
{"timeout", required_argument, 0, 't'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "vrf:t:h",
long_options, &option_index)) != -1) {
switch (opt) {
case 0:
// If option sets a flag, do nothing
break;
case 'v':
verbose = 1;
break;
case 'r':
recursive = 1;
break;
case 'f':
force = 1;
break;
case 't':
timeout = atoi(optarg);
break;
case 'h':
printf("Usage: %s [options] <input> [output]\n", argv[0]);
printf("Options:\n");
printf(" -v, --verbose Verbose output\n");
printf(" -r, --recursive Process recursively\n");
printf(" -f, --force Force overwrite\n");
printf(" -t, --timeout <seconds> Timeout value\n");
printf(" -h, --help Show this help\n");
return 0;
case '?':
// getopt_long already printed error
return 1;
default:
abort();
}
}
// Process remaining arguments
if (optind < argc) {
inputFile = argv[optind++];
}
if (optind < argc) {
outputFile = argv[optind++];
}
if (optind < argc) {
fprintf(stderr, "Error: Extra argument '%s'\n", argv[optind]);
return 1;
}
if (inputFile == NULL) {
fprintf(stderr, "Error: Input file required\n");
return 1;
}
printf("=== getopt_long() Parsing ===\n");
printf("Verbose: %s\n", verbose ? "yes" : "no");
printf("Recursive: %s\n", recursive ? "yes" : "no");
printf("Force: %s\n", force ? "yes" : "no");
printf("Timeout: %d seconds\n", timeout);
printf("Input file: %s\n", inputFile);
printf("Output file: %s\n", outputFile ? outputFile : "(none)");
return 0;
}
Complete Program Example: Calculator
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
typedef struct {
double operand1;
double operand2;
char operation;
int verbose;
int help;
} CalcConfig;
void printHelp(const char *progName) {
printf("Calculator - Command line calculator\n\n");
printf("Usage: %s [options] <operand1> <operation> <operand2>\n", progName);
printf("\nOperations:\n");
printf(" + Addition\n");
printf(" - Subtraction\n");
printf(" * Multiplication\n");
printf(" / Division\n");
printf(" %% Modulo (integers only)\n");
printf(" ^ Power\n");
printf("\nOptions:\n");
printf(" -v, --verbose Show detailed calculation\n");
printf(" -h, --help Show this help\n");
printf("\nExamples:\n");
printf(" %s 10 + 5\n", progName);
printf(" %s -v 7.5 * 3.2\n", progName);
printf(" %s 2 ^ 8\n", progName);
}
int parseArgs(int argc, char *argv[], CalcConfig *config) {
// Initialize defaults
config->verbose = 0;
config->help = 0;
config->operand1 = 0;
config->operand2 = 0;
config->operation = 0;
int i = 1;
// Parse options
while (i < argc && argv[i][0] == '-') {
if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
config->verbose = 1;
i++;
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
config->help = 1;
return 0;
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
return -1;
}
}
// Check we have exactly 3 arguments left
if (argc - i != 3) {
fprintf(stderr, "Error: Expected 3 arguments, got %d\n", argc - i);
return -1;
}
// Parse operand1
char *endptr;
config->operand1 = strtod(argv[i], &endptr);
if (*endptr != '\0') {
fprintf(stderr, "Error: Invalid number '%s'\n", argv[i]);
return -1;
}
i++;
// Parse operation
if (strlen(argv[i]) != 1) {
fprintf(stderr, "Error: Operation must be a single character\n");
return -1;
}
config->operation = argv[i][0];
i++;
// Parse operand2
config->operand2 = strtod(argv[i], &endptr);
if (*endptr != '\0') {
fprintf(stderr, "Error: Invalid number '%s'\n", argv[i]);
return -1;
}
return 0;
}
double power(double base, double exp) {
double result = 1.0;
int negative = (exp < 0);
if (negative) {
exp = -exp;
}
for (int i = 0; i < (int)exp; i++) {
result *= base;
}
if (negative) {
result = 1.0 / result;
}
return result;
}
int main(int argc, char *argv[]) {
CalcConfig config;
if (argc < 2) {
printHelp(argv[0]);
return 1;
}
if (parseArgs(argc, argv, &config) != 0) {
printHelp(argv[0]);
return 1;
}
if (config.help) {
printHelp(argv[0]);
return 0;
}
double result;
int error = 0;
char errorMsg[100] = "";
// Perform calculation
switch (config.operation) {
case '+':
result = config.operand1 + config.operand2;
break;
case '-':
result = config.operand1 - config.operand2;
break;
case '*':
result = config.operand1 * config.operand2;
break;
case '/':
if (config.operand2 == 0) {
strcpy(errorMsg, "Division by zero");
error = 1;
} else {
result = config.operand1 / config.operand2;
}
break;
case '%':
// Modulo requires integers
if ((int)config.operand1 != config.operand1 ||
(int)config.operand2 != config.operand2) {
strcpy(errorMsg, "Modulo requires integer operands");
error = 1;
} else if (config.operand2 == 0) {
strcpy(errorMsg, "Modulo by zero");
error = 1;
} else {
result = (int)config.operand1 % (int)config.operand2;
}
break;
case '^':
if (config.operand2 < 0 && config.operand2 != (int)config.operand2) {
strcpy(errorMsg, "Negative exponent must be integer");
error = 1;
} else {
result = power(config.operand1, config.operand2);
}
break;
default:
sprintf(errorMsg, "Unknown operation '%c'", config.operation);
error = 1;
}
// Output result
if (error) {
fprintf(stderr, "Error: %s\n", errorMsg);
return 1;
}
if (config.verbose) {
printf("Calculation: %.4f %c %.4f = ",
config.operand1, config.operation, config.operand2);
}
// Determine output format
if (result == (int)result) {
printf("%d\n", (int)result);
} else {
printf("%.6f\n", result);
}
return 0;
}
Practical Examples
1. File Information Tool
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
typedef struct {
char **files;
int fileCount;
int showSize;
int showPermissions;
int showOwner;
int showTime;
int longFormat;
} FileInfoConfig;
void printFileInfo(const char *filename, FileInfoConfig *config) {
struct stat st;
if (stat(filename, &st) != 0) {
fprintf(stderr, "Error: Cannot stat '%s'\n", filename);
return;
}
if (config->longFormat) {
// Permissions
if (config->showPermissions) {
printf( (S_ISDIR(st.st_mode)) ? "d" : "-");
printf( (st.st_mode & S_IRUSR) ? "r" : "-");
printf( (st.st_mode & S_IWUSR) ? "w" : "-");
printf( (st.st_mode & S_IXUSR) ? "x" : "-");
printf( (st.st_mode & S_IRGRP) ? "r" : "-");
printf( (st.st_mode & S_IWGRP) ? "w" : "-");
printf( (st.st_mode & S_IXGRP) ? "x" : "-");
printf( (st.st_mode & S_IROTH) ? "r" : "-");
printf( (st.st_mode & S_IWOTH) ? "w" : "-");
printf( (st.st_mode & S_IXOTH) ? "x" : "-");
printf(" ");
}
// Size
if (config->showSize) {
printf("%8ld ", st.st_size);
}
// Owner
if (config->showOwner) {
struct passwd *pw = getpwuid(st.st_uid);
struct group *gr = getgrgid(st.st_gid);
printf("%s %s ",
pw ? pw->pw_name : "unknown",
gr ? gr->gr_name : "unknown");
}
// Time
if (config->showTime) {
char timeStr[20];
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M",
localtime(&st.st_mtime));
printf("%s ", timeStr);
}
printf("%s\n", filename);
} else {
// Simple format
printf("%s\n", filename);
}
}
int main(int argc, char *argv[]) {
FileInfoConfig config = {0};
config.showSize = 1;
config.showPermissions = 1;
config.showOwner = 1;
config.showTime = 1;
int i = 1;
// Parse options
while (i < argc && argv[i][0] == '-') {
if (strcmp(argv[i], "-l") == 0) {
config.longFormat = 1;
i++;
} else if (strcmp(argv[i], "-s") == 0) {
config.showSize = !config.showSize;
i++;
} else if (strcmp(argv[i], "-p") == 0) {
config.showPermissions = !config.showPermissions;
i++;
} else if (strcmp(argv[i], "-o") == 0) {
config.showOwner = !config.showOwner;
i++;
} else if (strcmp(argv[i], "-t") == 0) {
config.showTime = !config.showTime;
i++;
} else if (strcmp(argv[i], "-h") == 0) {
printf("Usage: %s [options] <file1> <file2> ...\n", argv[0]);
printf("Options:\n");
printf(" -l Long format\n");
printf(" -s Toggle size display\n");
printf(" -p Toggle permissions display\n");
printf(" -o Toggle owner display\n");
printf(" -t Toggle time display\n");
printf(" -h Show this help\n");
return 0;
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
return 1;
}
}
// Check if files provided
if (i >= argc) {
fprintf(stderr, "Error: No files specified\n");
return 1;
}
// Process each file
config.files = &argv[i];
config.fileCount = argc - i;
for (int j = 0; j < config.fileCount; j++) {
printFileInfo(config.files[j], &config);
}
return 0;
}
2. Text Search Tool
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
typedef struct {
char *searchTerm;
char **files;
int fileCount;
int ignoreCase;
int showLineNumbers;
int countOnly;
int invertMatch;
} SearchConfig;
void searchFile(FILE *fp, const char *filename, SearchConfig *config) {
char *line = NULL;
size_t len = 0;
ssize_t read;
int lineNum = 0;
int matchCount = 0;
while ((read = getline(&line, &len, fp)) != -1) {
lineNum++;
// Remove newline
line[strcspn(line, "\n")] = 0;
char *lineToSearch = line;
char *termToSearch = config->searchTerm;
if (config->ignoreCase) {
lineToSearch = strdup(line);
termToSearch = strdup(config->searchTerm);
for (char *p = lineToSearch; *p; p++) *p = tolower(*p);
for (char *p = termToSearch; *p; p++) *p = tolower(*p);
}
int found = (strstr(lineToSearch, termToSearch) != NULL);
if (config->ignoreCase) {
free(lineToSearch);
free(termToSearch);
}
if (config->invertMatch) {
found = !found;
}
if (found) {
matchCount++;
if (!config->countOnly) {
if (config->fileCount > 1) {
printf("%s:", filename);
}
if (config->showLineNumbers) {
printf("%d:", lineNum);
}
printf("%s\n", line);
}
}
}
if (config->countOnly) {
if (config->fileCount > 1) {
printf("%s:", filename);
}
printf("%d\n", matchCount);
}
free(line);
}
int main(int argc, char *argv[]) {
SearchConfig config = {0};
if (argc < 2) {
fprintf(stderr, "Usage: %s [options] pattern [file1 file2 ...]\n", argv[0]);
fprintf(stderr, "Options:\n");
fprintf(stderr, " -i Ignore case\n");
fprintf(stderr, " -n Show line numbers\n");
fprintf(stderr, " -c Count only\n");
fprintf(stderr, " -v Invert match\n");
fprintf(stderr, " -h Show help\n");
return 1;
}
int i = 1;
// Parse options
while (i < argc && argv[i][0] == '-') {
if (strcmp(argv[i], "-i") == 0) {
config.ignoreCase = 1;
i++;
} else if (strcmp(argv[i], "-n") == 0) {
config.showLineNumbers = 1;
i++;
} else if (strcmp(argv[i], "-c") == 0) {
config.countOnly = 1;
i++;
} else if (strcmp(argv[i], "-v") == 0) {
config.invertMatch = 1;
i++;
} else if (strcmp(argv[i], "-h") == 0) {
fprintf(stderr, "Usage: %s [options] pattern [file1 file2 ...]\n", argv[0]);
return 0;
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
return 1;
}
}
// Get search pattern
if (i >= argc) {
fprintf(stderr, "Error: No search pattern specified\n");
return 1;
}
config.searchTerm = argv[i++];
config.files = &argv[i];
config.fileCount = argc - i;
// If no files specified, use stdin
if (config.fileCount == 0) {
searchFile(stdin, "(stdin)", &config);
} else {
// Search each file
for (int j = 0; j < config.fileCount; j++) {
FILE *fp = fopen(config.files[j], "r");
if (fp == NULL) {
fprintf(stderr, "Error: Cannot open '%s'\n", config.files[j]);
continue;
}
searchFile(fp, config.files[j], &config);
fclose(fp);
}
}
return 0;
}
Best Practices for Command Line Arguments
- Always Check argc: Verify sufficient arguments before accessing argv.
- Validate Input: Check that numeric arguments are valid numbers.
- Provide Help: Include
-hor--helpoption with usage information. - Use Standard Conventions: Follow POSIX conventions for option parsing.
- Handle Errors Gracefully: Print meaningful error messages and return non-zero exit codes.
- Document Arguments: Clearly document expected arguments and options.
- Support Both Short and Long Options: Use
getopt_long()for GNU-style options. - Quote Multi-word Arguments: Document that spaces in arguments need quotes.
- Check for Extra Arguments: Warn about unexpected arguments.
- Return Appropriate Exit Codes: 0 for success, non-zero for errors.
Common Exit Codes
#include <stdlib.h>
#define EXIT_SUCCESS 0 // Standard success
#define EXIT_FAILURE 1 // Standard failure
// Custom exit codes for specific errors
#define EXIT_INVALID_ARGS 2
#define EXIT_FILE_NOT_FOUND 3
#define EXIT_PERMISSION_DENIED 4
#define EXIT_OUT_OF_MEMORY 5
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return EXIT_INVALID_ARGS;
}
FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
perror("Error opening file");
return EXIT_FILE_NOT_FOUND;
}
// Process file...
fclose(fp);
return EXIT_SUCCESS;
}
Conclusion
Command line arguments transform C programs from static executables into flexible, reusable tools that integrate seamlessly with Unix/Linux environments. By mastering argument parsing techniques—from simple positional arguments to sophisticated option handling with getopt()—developers can create professional, user-friendly command-line applications.
The key to effective command line argument handling lies in robust validation, clear documentation, and consistent behavior with established conventions. Whether building simple utilities or complex applications, proper argument parsing ensures that programs behave predictably and helpfully, making them valuable components in scripts, build systems, and daily development workflows.
From the basic argc and argv to advanced getopt_long() parsing, C provides all the tools needed to create command-line interfaces that users expect and appreciate. With careful design and thorough error checking, command line arguments become a powerful interface between users and your C programs.