Part of C in 100s

C in 100 Seconds: Command Line Arguments

Celest KimCelest Kim

Video: C in 100 Seconds: Command Line Arguments — argc and argv | Episode 38 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

C Command Line Arguments: argc and argv

int main(int argc, char *argv[]). argc is 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 strncmp for --name=value style.

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.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.