Programming in C

Commandline Arguments

Gerald Senarclens de Grancy

Purpose of Commandline Arguments

  • Allow the user to pass information to a program upon startup
    • Which input files should be processed
      clang infile.c
    • Options can be defined
      ls -l -a -h
    • Possible subcommands
      apt install programname

Issues

  • Not all operating systems pass arguments in the same way
  • String parsing is tedious
  • POSIX getopt ( unistd.h) has limited features (eg. no long options)
  • 3rd party libraries offer differing feature sets
  • Argument parsing libraries are usually not trivial to use

Foundation of Argument Passing

In C, arguments are passed to main(.)

argc
argument count
argv
argument vector
int main(int argc, char** argv) {
  /* work with arguments */
  return 0;
}

Example: Echo Arguments Without Using a Library

#include <stdio.h>

int main(int argc, char **argv) {
  for (int i = 0; i < argc; ++i) {
    puts(argv[i]);
  }
  return 0;
}
Download echo.c

Example: Show a Simple Help Message

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

int main(int argc, char **argv) {
  for (int i = 1; i < argc; ++i) {
    if (strcmp("-h", argv[i]) == 0 ||
        strcmp("--help", argv[i]) == 0) {
      puts("Usage: ls [OPTION]... ");
      puts("-h, --help display this help and exit");
      exit(0);
    }
  }
  puts("To solve the world's problems, use the correct arguments");
  return 0;
}
Download help.c

POSIX Standard getopt

+
standardized
available (almost) everywhere
-
no support for long options
lack of support for subcommands etc.
#include <stdio.h>

#include <unistd.h>  /* for getopt */

int main (int argc, char **argv) {
  int c;
  int aopt = 0, bopt = 0;  // flags
  char *copt = 0, *dopt = 0;  // option arguments
  while ((c = getopt(argc, argv, "abc:d:")) != -1) {
    switch (c) {
    case 'a':
      aopt = 1;
      break;
    case 'b':
      bopt = 1;
      break;
    case 'c':
      copt = optarg;
      break;
    case 'd':
      dopt = optarg;
      break;
    default:
      printf ("?? getopt returned character code 0%o ??\n", c);
    }
  }
  if (optind < argc) {
      printf ("non-option ARGV-elements: ");
      while (optind < argc) {
          printf ("%s ", argv[optind++]);
      }
      printf ("\n");
  }
  printf ("aopt: %d, bopt: %d, copt: %s, dopt: %s\n",
    aopt, bopt, copt, dopt);
  return 0;
}
Download posix_getopt.c

GNU getopt_long

+
widely available GNU extenstion
supports long options
-
not available everywhere
lack of support for subcommands etc.
name
a long option's name
has_arg
no_argument | required_argument | optional_argument
arguments, if provided, are stored in optarg; otherwise optarg is NULL
flag
if flag is NULL, getopt_long returns val
if flag is a pointer to an int variable, val is assigned to the variable
and getopt_long returns 0
val
specifies the value returned by getopt_long or stored in the flag variable depending on the scenario
#include <getopt.h>  // getopt_long
#include <stdio.h>
#include <stdbool.h>

int main(int argc, char **argv) {
  int c;
  bool has_a = false, has_b = false;  // flags
  int verbosity_level = 0;
  char *c_arg = 0, *d_arg = 0;  // option_arguments
  static struct option long_options[] = {
  /* NAME       HAS_ARG            FLAG  VAL (SHORTNAME) */
    {"add",     required_argument, NULL, 0},
    {"append",  no_argument,       NULL, 1000},
    {"delete",  no_argument,       NULL, 0},
    {"verbose", no_argument,       NULL, 'v'},
    {"create",  required_argument, NULL, 'c'},
    {"file",    required_argument, NULL, 0},
    {NULL,      0,                 NULL, 0}
  };
  int option_index = 0;
  while ((c = getopt_long(argc, argv, "abc:d:vz",
          long_options, &option_index)) != -1) {
    switch (c) {
    case 0:
      printf ("option %s", long_options[option_index].name);
      if (optarg) {
        printf (" with arg %s", optarg);
      }
      printf ("\n");
      break;
    case 1000:  // append
      puts("`append` option was given");
      break;
    case 'a':
      has_a = true;
      break;
    case 'b':
      has_b = true;
      break;
    case 'c':
      c_arg = optarg;
      break;
    case 'd':
      d_arg = optarg;
      break;
    case 'v':
      ++verbosity_level;
    case '?':
      break;
    default:  // "catches" option 'z'
      printf ("?? getopt returned character code 0%o ??\n", c);
    }
  }
  if (optind < argc) {
    printf ("non-option ARGV-elements: ");
    while (optind < argc) {
      printf ("%s ", argv[optind++]);
    }
    printf ("\n");
  }
  printf ("a: %s, b: %s, c_arg: %s, d_arg: %s, verbosity-level: %d\n",
          has_a ? "true" : "false", has_b ? "true" : "false", c_arg, d_arg, verbosity_level);
  return 0;
}
Download gnu_getopt.c

Other Libraries

There are many libraries for argument parsing...

... select the one that suits your needs, eg.

argparse
https://github.com/cofyc/argparse
MIT license

Comprehensive Example(s)

Look at the source code of your favorite commanline programs...

This can be done via apt source $PACKAGE

eg. apt source htop

Or checkout a repository with an example
GNU getopt
eg. cvrptwms cli.c

Exercise

Write a C program that takes the following arguments

-h, --help
-a, --author
display your name and email address
--color {red,green,blue}
display a message in the given color

Questions
and feedback...