Command-line Arguments
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:
#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;
}gcc -std=c23 -Wall -Wextra args.c -o args
./args Pune 2026 "Meera Nair"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, andargv[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.
#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;
}./sum 120 80 250
./sum 120 8O 250Sum 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.
#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;
}./gst 1299 18
./gstPrice: 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:
#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;
}./greet -v Isha
./greetVerbose 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 isargv[1], andargcis one more than the number of arguments. - Reading
argv[1]without checkingargc. Always check the count first. - Using
atoi. It hides errors. Usestrtolorstrtodand checkendanderrno. - Printing errors to
stdout. Usefprintf(stderr, ...)so errors stay visible when output is redirected. - Returning 0 after an error. Return
EXIT_FAILUREso scripts can detect the problem.
Key points
- Declare
int main(int argc, char *argv[])to receive command-line arguments. argccounts the words including the program name;argv[0]is the program name andargv[argc]is a null pointer.- Arguments are strings; convert them with
strtolorstrtodand check for errors. - Check
argcfirst and print a usage message tostderrwhen arguments are wrong. - Loop over
argvand compare withstrcmpto handle options such as-vand--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
#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;
}./wordcount notes.txt3 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?
argc counts the program name too: ./tool, data.csv and -v make 3.
2. What is argv[0]?
argv[0] holds the program name; real arguments start at argv[1].
3. Why is strtol better than atoi for converting arguments?
With the end pointer and errno, strtol reports problems that atoi silently ignores.
4. Where should a command-line tool print its error messages?
stderr keeps errors separate from normal output, even when output is redirected.
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
