CLI#
C exposes command-line arguments through int main(int argc,
char **argv). argv[0] is the program name; argv[1]
through argv[argc-1] are the user’s arguments. The standard
parser is getopt (short options) / getopt_long (long
options); for richer UX the operator hand-rolls a parser or
pulls a tiny dependency.
For the I/O surface scripts read and write through, see I/O. For exit codes and process control, see Runtime.
main and argv#
#include <stdio.h>
int main(int argc, char **argv) {
printf("program: %s\n", argv[0]);
for (int i = 1; i < argc; i++) {
printf("%d: %s\n", i, argv[i]);
}
return 0;
}
$ ./scan -v --port 8080 host
program: ./scan
1: -v
2: --port
3: 8080
4: host
For programs that read environment variables, the optional
third parameter char **envp is available (or
extern char **environ).
getopt (short options)#
POSIX’s getopt from <unistd.h> parses short flags
(-v, -p 8080).
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char **argv) {
int verbose = 0;
int port = 80;
int c;
while ((c = getopt(argc, argv, "vp:")) != -1) {
switch (c) {
case 'v': verbose = 1; break;
case 'p': port = atoi(optarg); break;
case '?':
fprintf(stderr, "usage: %s [-v] [-p port] host\n", argv[0]);
return 2;
}
}
if (optind >= argc) {
fprintf(stderr, "%s: missing host\n", argv[0]);
return 2;
}
const char *host = argv[optind];
printf("%s %d %d\n", host, port, verbose);
return 0;
}
The format string "vp:" means -v is a flag and -p
takes a value (the :).
getopt_long (long options)#
GNU’s getopt_long (also in <getopt.h> on Linux) adds
--verbose / --port=8080 syntax.
#include <getopt.h>
int verbose = 0;
int port = 80;
static struct option long_opts[] = {
{"verbose", no_argument, &verbose, 1},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{0, 0, 0, 0}
};
int c, idx;
while ((c = getopt_long(argc, argv, "vp:h", long_opts, &idx)) != -1) {
switch (c) {
case 'v': verbose = 1; break;
case 'p': port = atoi(optarg); break;
case 'h': /* print help */ return 0;
}
}
strtol / strtoul / strtod#
Use strtol family for parsing numbers from
argv; atoi does not surface errors. end ends up
pointing at the first unconsumed character.
#include <stdlib.h>
#include <errno.h>
long parse_long(const char *s) {
char *end;
errno = 0;
long v = strtol(s, &end, 10);
if (errno != 0 || *end != '\0' || end == s) {
fprintf(stderr, "bad number: %s\n", s);
exit(2);
}
return v;
}
Exit codes#
exit(EXIT_SUCCESS) or return 0 from main. exit(n)
or return n for any small positive n on failure. The
convention.
Code |
Meaning (convention) |
|---|---|
0 |
success |
1 |
generic error |
2 |
usage error (bad command-line arguments) |
64-78 |
sysexits.h codes ( |
126 |
command found but not executable |
127 |
command not found |
128 + N |
terminated by signal N (e.g. 130 = SIGINT) |
exit(n) runs atexit handlers; _exit(n) skips them
(used after fork).
Streaming#
Filter-style C reads stdin, transforms, writes stdout.
#include <ctype.h>
#include <stdio.h>
int main(void) {
int c;
while ((c = getchar()) != EOF) {
putchar(toupper(c));
}
return 0;
}
$ echo -e "one\ntwo" | ./upper
ONE
TWO
Errors and diagnostics go to stderr (fprintf(stderr, ...)
or the perror helper).
Signals#
signal(SIGINT, handler) and the saner sigaction cover
clean shutdown on Ctrl-C.
#include <signal.h>
static volatile sig_atomic_t stop = 0;
static void on_sigint(int sig) { stop = 1; }
int main(void) {
struct sigaction sa = {.sa_handler = on_sigint};
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
while (!stop) work();
return 0;
}