Command-line Arguments

C · Lesson 27 of 32 7 min read Updated

Commands such as gcc hello.c -o hello or git commit -m "fix" take their instructions from the words you type after the program name. These words are command-line arguments. Your C programs can read them too, which turns them into real tools that scripts and other programs can use.

In this lesson you will read arguments with argc and argv, convert them to numbers safely, print a helpful usage message, handle simple options and return meaningful exit codes.

argc and argv

To receive arguments, declare main with two parameters:

C
#include <stdio.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]);
    }
    return 0;
}
Terminal
gcc -std=c23 -Wall -Wextra args.c -o args
./args Pune 2026 "Meera Nair"
Output
argc = 4
argv[0] = ./args
argv[1] = Pune
argv[2] = 2026
argv[3] = Meera Nair
  • argc (argument count) is the number of words, including the program name.
  • argv (argument vector) is an array of strings. argv[0] is the program name as it was typed (on Windows it may be the full path), argv[1] is the first real argument, and argv[argc] is always a null pointer.

The shell splits the command line at spaces, so a value containing spaces must be quoted, as "Meera Nair" shows. Every argument arrives as text, even 2026. To use it as a number, you must convert it.

Converting arguments safely with strtol

The old function atoi("2026") converts text to an int, but it cannot report errors: atoi("abc") returns 0 and atoi("8O") (with a letter O) returns 8, silently. Use strtol from stdlib.h instead. It also tells you where the number stopped, through a pointer you pass in, and it sets errno to ERANGE if the value is out of range.

C
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

// Converts text to an int. Returns true on success.
bool parse_int(const char *text, int *out)
{
    char *end;
    errno = 0;
    long value = strtol(text, &end, 10);
    if (end == text || *end != '\0') {
        return false;   // no digits, or extra characters after the number
    }
    if (errno == ERANGE || value < INT_MIN || value > INT_MAX) {
        return false;   // too big or too small
    }
    *out = (int) value;
    return true;
}

int main(int argc, char *argv[])
{
    int total = 0;
    for (int i = 1; i < argc; i++) {
        int n;
        if (!parse_int(argv[i], &n)) {
            fprintf(stderr, "Not a valid number: %s\n", argv[i]);
            return EXIT_FAILURE;
        }
        total += n;
    }
    printf("Sum of %d numbers: %d\n", argc - 1, total);
    return 0;
}
Terminal
./sum 120 80 250
./sum 120 8O 250
Output
Sum of 3 numbers: 450
Not a valid number: 8O

The third argument to strtol is the base: 10 for decimal. After the call, end points to the first character that was not part of the number. If end still equals text, there were no digits at all; if *end is not '\0', junk followed the number. Together with the range check, this rejects every bad input. The second run stops at 8O and returns EXIT_FAILURE, so a script calling it knows that something went wrong. For decimal numbers, strtod works the same way and returns a double.

A complete tool with a usage message

A good command-line tool checks argc first and explains how to use it when the arguments are wrong. Error messages go to stderr, so they are not mixed into normal output that might be redirected to a file.

C
#include <stdio.h>
#include <stdlib.h>

bool parse_double(const char *text, double *out)
{
    char *end;
    double value = strtod(text, &end);
    if (end == text || *end != '\0' || value < 0) {
        return false;
    }
    *out = value;
    return true;
}

int main(int argc, char *argv[])
{
    if (argc != 3) {
        fprintf(stderr, "Usage: %s PRICE GST_PERCENT\n", argv[0]);
        fprintf(stderr, "Example: %s 1299 18\n", argv[0]);
        return EXIT_FAILURE;
    }

    double price, rate;
    if (!parse_double(argv[1], &price) || !parse_double(argv[2], &rate)) {
        fprintf(stderr, "Price and GST must be non-negative numbers\n");
        return EXIT_FAILURE;
    }

    double gst = price * rate / 100;
    printf("Price: %.2f\n", price);
    printf("GST:   %.2f\n", gst);
    printf("Total: %.2f\n", price + gst);
    return 0;
}
Terminal
./gst 1299 18
./gst
Output
Price: 1299.00
GST:   233.82
Total: 1532.82
Usage: ./gst PRICE GST_PERCENT
Example: ./gst 1299 18

Using argv[0] in the usage message means the message always shows the name the user actually typed. Checking argc before reading argv[1] is essential: with no arguments, argv[1] is a null pointer, and using it as a string crashes the program.

Handling options

Many tools accept options that start with - or --. A simple loop with strcmp handles a few of them:

C
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    bool verbose = false;
    const char *name = "friend";

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
            verbose = true;
        } else if (strcmp(argv[i], "--help") == 0) {
            printf("Usage: %s [-v] [NAME]\n", argv[0]);
            return 0;
        } else if (argv[i][0] == '-') {
            fprintf(stderr, "Unknown option: %s\n", argv[i]);
            return 1;
        } else {
            name = argv[i];
        }
    }

    if (verbose) {
        printf("Verbose mode is on\n");
    }
    printf("Hello, %s!\n", name);
    return 0;
}
Terminal
./greet -v Isha
./greet
Output
Verbose mode is on
Hello, Isha!
Hello, friend!

Options may appear in any order, and anything that does not start with - is treated as the name. --help prints the usage and exits successfully. On Linux and macOS, the POSIX function getopt from unistd.h parses options in the standard Unix style, including combined flags such as -vq. It is not part of standard C, so it is not available with every Windows compiler.

Exit codes

The value main returns becomes the program's exit code, as you saw in Your First C Program. Command-line tools follow a convention: 0 means success, 1 (or EXIT_FAILURE) means a general error, and some tools use 2 for wrong usage. Scripts rely on these codes, for example a shell runs ./gst 1299 18 && echo done and prints done only when the program succeeds.

Common mistakes

  • Forgetting that argv[0] is the program name. The first real argument is argv[1], and argc is one more than the number of arguments.
  • Reading argv[1] without checking argc. Always check the count first.
  • Using atoi. It hides errors. Use strtol or strtod and check end and errno.
  • Printing errors to stdout. Use fprintf(stderr, ...) so errors stay visible when output is redirected.
  • Returning 0 after an error. Return EXIT_FAILURE so scripts can detect the problem.

Key points

  • Declare int main(int argc, char *argv[]) to receive command-line arguments.
  • argc counts the words including the program name; argv[0] is the program name and argv[argc] is a null pointer.
  • Arguments are strings; convert them with strtol or strtod and check for errors.
  • Check argc first and print a usage message to stderr when arguments are wrong.
  • Loop over argv and compare with strcmp to handle options such as -v and --help.
  • Return 0 for success and a non-zero exit code for errors.

Exercise

Write a tool wordcount that takes one file name as an argument and prints the number of lines, words and characters in that file. Print a usage message if the number of arguments is wrong, and use perror if the file cannot be opened. Test it on a file notes.txt containing the three lines C is fast, Pointers need care and Practice daily.

Show solution
C
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s FILE\n", argv[0]);
        return EXIT_FAILURE;
    }

    FILE *fp = fopen(argv[1], "r");
    if (fp == nullptr) {
        perror(argv[1]);
        return EXIT_FAILURE;
    }

    long lines = 0, words = 0, chars = 0;
    bool in_word = false;
    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        chars++;
        if (ch == '\n') {
            lines++;
        }
        if (isspace(ch)) {
            in_word = false;
        } else if (!in_word) {
            in_word = true;
            words++;
        }
    }
    fclose(fp);

    printf("%ld lines, %ld words, %ld characters\n", lines, words, chars);
    return 0;
}
Terminal
./wordcount notes.txt
Output
3 lines, 8 words, 44 characters

fgetc returns an int, so it can return the special value EOF at the end of the file. A word starts whenever a non-space character follows a space, and every newline counts as a character too.

Quick Quiz

1. For the command ./tool data.csv -v, what is argc?

2. What is argv[0]?

3. Why is strtol better than atoi for converting arguments?

4. Where should a command-line tool print its error messages?

Learn C with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call