Command Line Arguments

Every command line tool you use takes arguments. When you type gcc -o myprogram main.c, you’re passing four arguments to gcc. When you type ls -la /home, you’re passing two arguments to ls.

Now you’ll learn how to make your own programs accept arguments. This is how real tools work.

What Are Command Line Arguments?

Command line arguments are the extra words you type after a program’s name:

./myprogram hello world 123

Here, hello, world, and 123 are arguments. They’re information you pass to the program when it starts.

Think of it like calling someone and giving them instructions right away: “Hey, copy these three files to that folder.” You don’t want to start the program and then answer questions one by one. You want to tell it everything upfront.

argc and argv

Until now, your main function looked like this:

int main(void) {
    // Your code
    return 0;
}

To receive command line arguments, you change it to:

int main(int argc, char *argv[]) {
    // Your code
    return 0;
}

Two new parameters:

  • argc - “argument count” - how many arguments were passed (including the program name)
  • argv - “argument vector” - an array of strings containing the actual arguments

The program name itself is always the first argument. So if you run:

./myprogram hello world

Then:

  • argc is 3 (the program name plus two arguments)
  • argv[0] is "./myprogram"
  • argv[1] is "hello"
  • argv[2] is "world"

Your First Program with Arguments

Let’s see them in action:

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("Number of arguments: %d\n", argc);
    printf("Program name: %s\n", argv[0]);

    return 0;
}

Compile and run it:

$ gcc -o args args.c
$ ./args
Number of arguments: 1
Program name: ./args

With no extra arguments, argc is 1 - just the program name.

$ ./args hello world
Number of arguments: 3
Program name: ./args

Now argc is 3.

Printing All Arguments

Since argv is just an array, you can loop through it:

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("You passed %d argument(s):\n", argc);

    for (int i = 0; i < argc; i++) {
        printf("  argv[%d] = %s\n", i, argv[i]);
    }

    return 0;
}
$ ./args one two three
You passed 4 argument(s):
  argv[0] = ./args
  argv[1] = one
  argv[2] = two
  argv[3] = three

Building a Simple Echo Program

The Unix echo command just prints whatever you give it. Let’s build our own:

#include <stdio.h>

int main(int argc, char *argv[]) {
    // Start at 1 to skip the program name
    for (int i = 1; i < argc; i++) {
        printf("%s", argv[i]);

        // Print space between words, but not after the last one
        if (i < argc - 1) {
            printf(" ");
        }
    }
    printf("\n");

    return 0;
}
$ ./echo Hello World
Hello World
$ ./echo This is a test
This is a test

That’s a real tool. Simple, but useful.

Arguments Are Always Strings

Here’s something important: every argument is a string, even if it looks like a number.

$ ./myprogram 42

The 42 isn’t the number 42 - it’s the string “42”. Two characters: ‘4’ and ‘2’.

If you want to use it as a number, you need to convert it.

Converting Strings to Numbers

The standard library gives you functions to convert strings to numbers:

  • atoi() - string to int (stands for “ASCII to integer”)
  • atol() - string to long
  • atof() - string to double (float)

You need to include <stdlib.h> to use these.

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

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <number>\n", argv[0]);
        return 1;
    }

    int num = atoi(argv[1]);
    printf("You entered: %d\n", num);
    printf("Doubled: %d\n", num * 2);

    return 0;
}
$ ./double 21
You entered: 21
Doubled: 42

A Command Line Calculator

Let’s build something more useful - a calculator that takes numbers from the command line:

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

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("Usage: %s <num1> <operator> <num2>\n", argv[0]);
        printf("Example: %s 10 + 5\n", argv[0]);
        return 1;
    }

    int a = atoi(argv[1]);
    char *op = argv[2];
    int b = atoi(argv[3]);
    int result;

    if (strcmp(op, "+") == 0) {
        result = a + b;
    } else if (strcmp(op, "-") == 0) {
        result = a - b;
    } else if (strcmp(op, "x") == 0) {
        // Use 'x' for multiply because '*' has special meaning in shells
        result = a * b;
    } else if (strcmp(op, "/") == 0) {
        if (b == 0) {
            printf("Error: Cannot divide by zero\n");
            return 1;
        }
        result = a / b;
    } else {
        printf("Unknown operator: %s\n", op);
        printf("Use: + - x /\n");
        return 1;
    }

    printf("%d %s %d = %d\n", a, op, b, result);
    return 0;
}
$ ./calc 10 + 5
10 + 5 = 15
$ ./calc 100 / 4
100 / 4 = 25
$ ./calc 7 x 6
7 x 6 = 42

Notice we use x instead of * for multiplication. The asterisk has special meaning in most shells (it matches filenames), so we avoid it.

Checking for the Right Number of Arguments

Most programs need a specific number of arguments. Always check:

#include <stdio.h>

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Usage: %s <source> <destination>\n", argv[0]);
        return 1;
    }

    printf("Copying from %s to %s\n", argv[1], argv[2]);
    // ... actual copying code would go here

    return 0;
}
$ ./copy
Usage: ./copy <source> <destination>
$ ./copy file1.txt
Usage: ./copy <source> <destination>
$ ./copy file1.txt file2.txt
Copying from file1.txt to file2.txt

This is called a “usage message.” Good programs always tell you how to use them correctly.

Returning Error Codes

Notice we return 1 when something goes wrong. This is a convention:

  • Return 0 for success
  • Return non-zero for errors

Other programs can check this return value to know if your program worked:

./myprogram && echo "Success!" || echo "Failed!"

Parsing Flags

Many programs accept flags like -v for verbose or -h for help. Here’s how to handle them:

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

int main(int argc, char *argv[]) {
    int verbose = 0;
    int count = 1;

    // Check each argument for flags
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-v") == 0) {
            verbose = 1;
        } else if (strcmp(argv[i], "-h") == 0) {
            printf("Usage: %s [-v] [-h]\n", argv[0]);
            printf("  -v  Verbose mode\n");
            printf("  -h  Show this help\n");
            return 0;
        }
    }

    if (verbose) {
        printf("Verbose mode enabled\n");
        printf("About to count to %d\n", count);
    }

    for (int i = 1; i <= count; i++) {
        printf("%d\n", i);
    }

    if (verbose) {
        printf("Done counting\n");
    }

    return 0;
}
$ ./counter
1
$ ./counter -v
Verbose mode enabled
About to count to 1
1
Done counting
$ ./counter -h
Usage: ./counter [-v] [-h]
  -v  Verbose mode
  -h  Show this help

Flags with Values

Sometimes flags take values, like -n 5 to set a count:

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

int main(int argc, char *argv[]) {
    int verbose = 0;
    int count = 10;  // Default value

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-v") == 0) {
            verbose = 1;
        } else if (strcmp(argv[i], "-n") == 0) {
            // Make sure there's a next argument
            if (i + 1 >= argc) {
                printf("Error: -n requires a number\n");
                return 1;
            }
            count = atoi(argv[i + 1]);
            i++;  // Skip the next argument since we used it
        } else if (strcmp(argv[i], "-h") == 0) {
            printf("Usage: %s [-v] [-n count] [-h]\n", argv[0]);
            printf("  -v       Verbose mode\n");
            printf("  -n NUM   Count to NUM (default: 10)\n");
            printf("  -h       Show this help\n");
            return 0;
        }
    }

    if (verbose) {
        printf("Counting to %d:\n", count);
    }

    for (int i = 1; i <= count; i++) {
        printf("%d\n", i);
    }

    return 0;
}
$ ./counter -n 3
1
2
3
$ ./counter -v -n 5
Counting to 5:
1
2
3
4
5

A Complete Example: Word Counter

Let’s build a tool that counts words in files passed as arguments:

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

int count_words(const char *filename) {
    FILE *file = fopen(filename, "r");
    if (file == NULL) {
        return -1;  // Error
    }

    int words = 0;
    int in_word = 0;
    int c;

    while ((c = fgetc(file)) != EOF) {
        if (isspace(c)) {
            in_word = 0;
        } else if (!in_word) {
            in_word = 1;
            words++;
        }
    }

    fclose(file);
    return words;
}

int main(int argc, char *argv[]) {
    int show_total = 0;

    if (argc < 2) {
        printf("Usage: %s [-t] file1 [file2 ...]\n", argv[0]);
        printf("  -t  Show total word count\n");
        return 1;
    }

    int total = 0;
    int file_count = 0;

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-t") == 0) {
            show_total = 1;
            continue;
        }

        int words = count_words(argv[i]);
        if (words < 0) {
            printf("Error: Cannot open %s\n", argv[i]);
        } else {
            printf("%8d %s\n", words, argv[i]);
            total += words;
            file_count++;
        }
    }

    if (show_total && file_count > 1) {
        printf("%8d total\n", total);
    }

    return 0;
}
$ ./wc file1.txt file2.txt -t
     156 file1.txt
     243 file2.txt
     399 total

This is starting to feel like a real Unix tool.

Better Number Conversion: strtol

The atoi function has a problem - it can’t tell you if the conversion failed. If you pass “hello” to atoi, it just returns 0. Was that a real 0, or an error?

For more robust code, use strtol:

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

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <number>\n", argv[0]);
        return 1;
    }

    char *endptr;
    errno = 0;
    long num = strtol(argv[1], &endptr, 10);

    // Check for errors
    if (errno != 0) {
        printf("Error: Number out of range\n");
        return 1;
    }
    if (*endptr != '\0') {
        printf("Error: '%s' is not a valid number\n", argv[1]);
        return 1;
    }

    printf("You entered: %ld\n", num);
    return 0;
}
$ ./num 42
You entered: 42
$ ./num hello
Error: 'hello' is not a valid number
$ ./num 12abc
Error: '12abc' is not a valid number

The strtol function sets endptr to point to where it stopped parsing. If it points to the null terminator, the whole string was valid. If it points somewhere else, there was garbage in the string.

What You’ve Learned

  • argc tells you how many arguments were passed
  • argv is an array of strings containing the arguments
  • argv[0] is always the program name
  • All arguments are strings - use atoi/strtol to convert to numbers
  • Check argc to make sure you got the right number of arguments
  • Use strcmp to check for flags like -v or -h
  • Return 0 for success, non-zero for errors

Try It Yourself

  1. Write a program that takes a filename and prints its contents (like a simple cat command)

  2. Create a program that takes two numbers and prints their sum, difference, product, and quotient

  3. Build a “reverse echo” that prints its arguments in reverse order

  4. Make a program with -u flag to convert arguments to uppercase and -l for lowercase

  5. Create a simple grep - take a search term and filename, print lines containing that term

Common Mistakes

  • Forgetting argv[0] is the program name. Real arguments start at argv[1].

  • Not checking argc before accessing argv. If argc is 1 and you access argv[1], that’s undefined behavior.

  • Assuming arguments are numbers. argv[1] is always a string. Use atoi() or strtol() to convert.

  • Forgetting atoi returns 0 for invalid input. atoi(“hello”) returns 0, not an error. Use strtol() if you need proper error checking.

  • Using * for multiplication. The shell interprets * as a wildcard. Use ‘x’ or quote it: ./calc 5 '*' 3 or ./calc 5 x 3.

  • Off-by-one errors in loops. Loop from 1 to argc (not 0 to argc-1) when you want to skip the program name, but make sure your condition is i < argc, not i <= argc.

Next Up

In Part 25, we’ll learn about header files and multi-file projects - organizing your code across multiple files for larger, maintainable programs.


Enjoyed This?

If this helped something click, subscribe to my YouTube channel. More content like this, same approach - making things stick without insulting your intelligence. It’s free, it helps more people find this stuff, and it tells me what’s worth making more of.