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 (EX_USAGE, EX_NOINPUT, EX_NOPERM, …); rare but standard on BSD.

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;
}

References#