C in 100 Seconds: Command Line Arguments
Video: C in 100 Seconds: Command Line Arguments — argc and argv | Episode 38 by Taught by Celeste AI - AI Coding Coach
C Command Line Arguments: argc and argv
int main(int argc, char *argv[]).argcis the count;argv[0]is the program name;argv[1...argc-1]are the arguments. The bridge between the shell and your program.
When you run ./prog -v -n 5 hello, the shell passes -v, -n, 5, hello as separate strings. Your program receives them via argc and argv.
The basic shape
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
printf("argc: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf(" argv[%d] = %s\n", i, argv[i]);
}
// Parse flags
int verbose = 0;
int count = 1;
char *name = "World";
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0) {
verbose = 1;
} else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
count = atoi(argv[++i]);
} else {
name = argv[i];
}
}
for (int i = 0; i < count; i++) {
printf("Hello, %s!\n", name);
}
if (verbose) {
printf("(repeated %d times)\n", count);
}
return 0;
}
What argc and argv contain
For ./hello -v -n 3 World:
argc = 5— total argument count.argv[0] = "./hello"— program name as invoked.argv[1] = "-v"argv[2] = "-n"argv[3] = "3"argv[4] = "World"argv[5] = NULL— guaranteed sentinel.
The shell does the splitting. Quotes group: ./prog "hello world" gives one argument with a space.
argv[0] is the invocation
printf("Running as: %s\n", argv[0]);
// "Running as: ./hello"
// or "Running as: hello" (if invoked through PATH)
// or "Running as: /usr/local/bin/hello" (full path)
argv[0] is whatever the shell passed. It can be the relative path, full path, or just the name. Don't rely on it being a specific format.
For displaying the program name in error messages, argv[0] is conventional — but consider stripping the directory:
const char *prog = strrchr(argv[0], '/');
prog = prog ? prog + 1 : argv[0];
fprintf(stderr, "%s: error: ...\n", prog);
Manual flag parsing
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
verbose = 1;
} else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
count = atoi(argv[++i]);
} else if (strncmp(argv[i], "--name=", 7) == 0) {
name = argv[i] + 7; // skip the prefix
} else if (argv[i][0] != '-') {
// positional argument
name = argv[i];
} else {
fprintf(stderr, "Unknown flag: %s\n", argv[i]);
return 1;
}
}
Walk argv from index 1 (skip program name). For each arg:
- Match flags by string.
- For flags with values (
-n 5), advance the index to consume the value. - Use
strncmpfor--name=valuestyle.
For more elaborate parsing, use getopt (POSIX) or argp (GNU).
getopt — POSIX standard
#include <unistd.h>
int main(int argc, char *argv[]) {
int verbose = 0;
int count = 1;
int opt;
while ((opt = getopt(argc, argv, "vn:")) != -1) {
switch (opt) {
case 'v': verbose = 1; break;
case 'n': count = atoi(optarg); break;
default:
fprintf(stderr, "Usage: %s [-v] [-n count] name\n", argv[0]);
return 1;
}
}
// After getopt, optind is the index of the first non-flag argument
if (optind < argc) {
char *name = argv[optind];
// ...
}
}
getopt(argc, argv, "vn:"):
"v"— flag with no value."n:"— flag requiring a value (the colon).
optarg is the value (for -n 5, it's "5").
optind is the position after parsing — where the positional args start.
For long options (--verbose), use getopt_long.
atoi vs strtol
int n = atoi(argv[2]); // returns 0 on failure (can't distinguish from "0")
char *endptr;
long n = strtol(argv[2], &endptr, 10);
if (*endptr != '\0') {
// not a valid number
}
atoi is simple but can't tell you if parsing failed. strtol sets endptr to point past the parsed number — if it points to \0, the whole string was a number.
For real input parsing, prefer strtol.
stdin / stdout / pipes
// Read from a piped input
int x;
if (scanf("%d", &x) == 1) {
printf("got %d\n", x);
}
echo 42 | ./prog
# "got 42"
When stdin is connected to a pipe or file, your program reads from it normally. No special handling needed — same scanf/fgets API.
For a "filter"-style program (Unix tool):
int main(int argc, char *argv[]) {
char line[256];
while (fgets(line, sizeof(line), stdin) != NULL) {
// process line
fputs(line, stdout);
}
return 0;
}
./prog < input.txt > output.txt redirects stdin and stdout via the shell.
Environment variables
#include <stdlib.h>
const char *home = getenv("HOME");
const char *path = getenv("PATH");
Not technically argv, but related. getenv(name) reads an environment variable. Returns NULL if not set.
For a third main signature: int main(int argc, char *argv[], char *envp[]) — POSIX provides envp. Less common; getenv is preferred.
Common mistakes
Indexing past argc. argv[argc] is NULL; reading further is undefined. Always bounds-check.
Modifying argv strings. They're typically writable (POSIX), but not guaranteed. Treat as read-only.
atoi without validation. Bad input silently becomes 0. Use strtol for real validation.
Forgetting argv[0] is the program name. Loops should start at i = 1 for arguments.
Flag parsing without -- separator. Some programs use -- to mean "everything after is positional, not a flag." Implement if your tool's UX needs it.
Hardcoded help text out of sync. If your --help text doesn't match the actual flags, users get confused. Generate from a single source if possible.
What's next
Episode 39: error handling — errno, perror, strerror. What to do when system calls fail.
Recap
int main(int argc, char *argv[]). argv[0] is the program name; argv[1..argc-1] are args; argv[argc] is NULL. Walk from i = 1. For simple flag parsing, manual loop with strcmp works. For complex needs, use getopt (POSIX) or getopt_long. Use strtol instead of atoi for validation. Pipe input read via stdin (scanf/fgets). Environment via getenv.
Next episode: error handling.