The Hackerlab at `regexps.com`

## Parsing Command-line Options

up: libhackerlab prev: File-name Manipulation Functions

The functions and macros in this chapter provide a convenient and standard way to parse command line arguments and provide on-line help for command-line invocation. An example at the end of the chapter illustrates their use.

It is important to understand that these functions are designed to support consistent command line interfaces, rather than arbitrary command line interfaces. Consequently, these functions aren't especially useful for implementing some of the standard Posix command line syntaxes.

- Option Tables
- Parsing Options
- Printing Help Messages
- Shifting Arguments
- An Example of Option Parsing

### Option Tables

up: Parsing Command-line Options next: Parsing Options

Type `opt_desc`

    struct opt_desc
    {
      int opt_value;
      t_uchar * char_name;
      t_uchar * long_name;
      int requires_arg;
      t_uchar * desc;
    };

Type `opt_parsed`

    struct opt_parsed
    {
      int opt_value;
      t_uchar * opt_string;
      t_uchar * arg_string;
      struct opt_desc * desc;
    };

An array of `struct opt_desc` is used to describe the options accepted by a program.

The easiest way to create this array is by defining a macro `OPTS` of two arguments: `OP` , which is used to define a program option and `OP2` , which is used to add additional lines of documentation to an option.

Both `OP` and `OP2` are used as macros which accept `5` arguments:

         name            an enum name for the option
         char_name       0 or a string beginning with a one-character
                         name for the option.
         long_name       0 or a string beginning with a long name for
                         the option
         arg             1 if the option requires an argument, 0
                         otherwise
         desc            A documentation string for the option

Once you have defined `OPTS` it is easy to create a table of `struct opt_desc` , as in the following example:

    #define OPTS(OP, OP2) \
      OP (opt_help_msg,     /* An enum name for the option */ \
          "h",              /* A short name for the option */ \
          "help",           /* A long name for the option */ \
          0,                /* The option takes no arguments */ \
          "Display a help message and exit.") /*  help message */ \
      \
      OP (opt_version, "V", "version", 0, \
               "Display a release identifier string and exit.") \
      \
      /* The next option illustrates how to handle a multi-line */ \
      /* help message: */ \
      \
      OP (opt_output_file, "o file", "output-file=file", 1, \
               "Write output to FILE.") \
      OP2 (opt_output_file, 0, 0, 1, \
                "The file must not already exist.") \
      OP (...) ...

    /* Note that the short and long names for an option are optional
     * (may be 0) but if both are omitted, then there is no way to
     * specify the option on a command line.
     */

    enum options
    {
      OPTS (OPT_ENUM, OPT_IGN)
    };

    struct opt_desc opts[] =
    {
      OPTS (OPT_DESC, OPT_DESC)
        {-1, 0, 0, 0, 0}
    };

### Parsing Options

up: Parsing Command-line Options next: Printing Help Messages prev: Option Tables

Function `opt_low`

    int opt_low (t_uchar * opt_string,
                 t_uchar * opt_arg,
                 struct opt_desc ** desc,
                 struct opt_desc * opts,
                 int * argcp,
                 char ** argv,
                 t_uchar * opt_string_space);

Return the next command line option and modify argc/argv to remove that option.

`opts` should point to an array of `struct opt_desc` whose final element contains a value less than `0` in the field `opt_value` .

Ordinarily `opt` returns the `opt_value` field from the element of `opts` that matches the next argument. If the field `requires_arg` is not `0` , then the option argument is also returned. The option and its argument are removed from argc/argv. `opt_string` returns a string naming the option ( -x , --long-opt or --long-opt=value ). The string returned in `opt_string` may be overwritten by later calls to `opt` . `opt_arg` returns the argument to the option, if the argument requires an argument and one was provided. A string returned in `opt_arg` points to a string or substring from `argv` . `desc` returns the option description for the option returned.

Any of `opt_string` , `opt_arg` , and `desc` may be `0` .

`opt_string_space` should point to an array of three characters. If the next option is a short option, the value stored in `*opt_string` will point to `opt_string_space` . `opt_string_space` may be `0` if `opt_string` is `0` .

If the option requires an argument but none is provided, `*opt_arg` is set to `0` , and the `opt_value` field of the option is returned; argc/argv is not modified. To see that an option with required argument has been correctly provided, it is necessary to check both the return value of `opt` , and the value of `*opt_arg` . This behavior is to facilitate providing a specific error message to the user -- to quote POSIX: option arguments may not be optional .

If the next argument is not an ordinary option, a value less than `0` is returned. These special values are given symbolic names in `opt.h` (`enum opt_return_values` ):

                 opt_dash        -- the next argument is simply "-".
                 opt_double_dash -- the next argument is simply "--".

In either case, the argument ( - or -- ) is removed from argc/argv.

                 opt_unknown     -- the next argument does not match
                                    any known option.

                 opt_bogus_argument -- a recognized long option was
                                       provided with an option argument,
                                       but the option does not accept
                                       an argument.

                 opt_none        -- the next argument does not begin
                                    with "-".

In any of those cases, argc/argv is not modified.

Function `opt_standard`

    int opt_standard (alloc_limits limits,
                      struct opt_parsed ** parsed_p,
                      struct opt_desc * opts,
                      int * argcp,
                      char * argvp[],
                      t_uchar * program_name,
                      t_uchar * usage,
                      t_uchar * version_string,
                      t_uchar * long_help,
                      int help_option,
                      int long_help_option,
                      int version_option);

This function calls `opt` , but handles some commonly implemented options internally, in a standard way.

The parameters `limits` , `parsed_p` , `opts` , `argcp` , and `argvp` are as to `opt_low` .

The parameters `program_name` , `usage` and `version_string` are used in error and informational messages printed by this function.

The parameters `help_option` , `long_help_option` and `version_option` are the `name` value for some standard options, which are usually given the command line names:

         -h      --help          help_option
         -H                      long_help_option
         -V      --version       version_option

If this function detects the `help_option` , it prints a multi-line message summarizing the available options to standard output and exits the process with a `0` status.

If this function detects the `long_help_option` , it prints a multi-line message summarizing the available options to standard output, adds text from `long_help` and exits the process with a `0` status. The first line of `long_help` should summarize the command (conventionally uncapitalized and without punctuation). The rest of `long_help` should expand upon the explanation. Long help messages are formatted as:

         first line of long_help
         usage: program_name ....
         option list

         rest of long_help

If this function detects the `version_option` , it prints a single-line message containing `version_string` to standard output and exits the process with a `0` status.

If this function detects an unrecognized option, a missing option argument, or an argument provided for an option that doesn't accept arguments, it prints a usage message to standard error and exits the process with a non-0 status.

### Printing Help Messages

up: Parsing Command-line Options next: Shifting Arguments prev: Parsing Options

Function `opt_usage`

    void opt_usage (int fd,
                    char * argv0,
                    t_uchar * program_name,
                    t_uchar * usage,
                    int suggest_help);

Print a usage message on `fd` using the format:

         "usage: %s %s\n", program_name, usage

If `suggest_help` is not `0` , also print:

         "try %s --help\n", argv0

By convention, `argv0` should be the first command line string passed to the program (the name by which the program was invoked).

`program_name` should be the default name for the program (not necessarily the name by which it was invoked).

Function `opt_help`

    void opt_help (int fd, struct opt_desc * opts);

Print a help message based on `opts` .

A long option name in `opts` can have the form --foo=BAR . The text =BAR is included in the help message.

### Shifting Arguments

up: Parsing Command-line Options next: An Example of Option Parsing prev: Printing Help Messages

Function `opt_shift_char_option`

    void opt_shift_char_option (int * argcp, char ** argv);

Remove a single character argument from argc/argv. For example,

Before:

         *argcp = 3
         argv = {"prog", "-abc", "-def", 0}

After:

         *argcp = 3
         argv = {"prog", "-bc", "-def", 0}

Before:

         *argcp = 3
         argv = {"prog", "-a", "-def", 0}

After:

         *argcp = 2
         argv = {"prog", "-def", 0}

Function `opt_shift`

    void opt_shift (int * argcp, char ** argv);

Remove a single argument from argc/argv. For example,

Before:

         *argcp = 3
         argv = {"prog", "--abc", "-def", 0}

After:

         *argcp = 2
         argv = {"prog", "-def", 0}

### An Example of Option Parsing

up: Parsing Command-line Options prev: Shifting Arguments

The following program illustrates option parsing with `opt` . These examples illustrates its usage:

         % test --version
         test 1.0.0

         % test --help
         usage: ./,test [options] [input-file]

           -h, --help                   Display a help message and exit.
           -V, --version                Display a release identifier string
                                        and exit.
           -o file, --output-file=file  Write output to FILE.

         % ./,test -o one --output-file two -othree --output-file=four
         output file argument: `one'
         output file argument: `two'
         output file argument: `three'
         output file argument: `four'

         % ./,test -xyzzy
         unhandled option `-xyzzy'
         usage: ./,test [options] [input-file]
         try ./,test --help

Here's the code:

    #include "hackerlab/cmd/main.h"

    static t_uchar * program_name = "test";
    static t_uchar * usage = "[options]";
    static t_uchar * version_string = "1.0";

    #define OPTS(OP, OP2) \
      OP (opt_help_msg, "h", "help", 0, \
          "Display a help message and exit.") \
      OP (opt_version, "V", "version", 0, \
          "Display a release identifier string") \
      OP2 (opt_version, 0, 0, 0, "and exit.") \
      OP (opt_output_file, "o file", "output-file=file", 1, \
          "Write output to FILE.")

    enum options
    {
      OPTS (OPT_ENUM, OPT_IGN)
    };

    struct opt_desc opts[] =
    {
      OPTS (OPT_DESC, OPT_DESC)
        {-1, 0, 0, 0, 0}
    };

    int
    main (int argc, char * argv[])
    {
      int errn;
      int o;
      struct opt_parsed * option;

      option = 0;

      while (1)
        {
          o = opt_standard (lim_use_must_malloc,
                            &option,
                            opts,
                            &argc, argv,
                            program_name,
                            usage,
                            version_string,
                            opt_help_msg,
                            opt_version);
          if (o == opt_none)
            break;
          switch (o)
            {
            default:
              safe_printfmt (2, "unhandled option `%s'\n",
                             option->opt_string);
              panic ("internal error parsing arguments");

            usage_error:
              opt_usage (2, argv[0], program_name, usage, 1);
              panic_exit ();

            bogus_arg:
              safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n",
                             option->opt_string,
                             option->arg_string);
              goto usage_error;

            case opt_output_file:
              if (!opt_arg)
                {
                  printfmt (&errn,
                            2,
                            "missing argument for `%s'\n",
                            opt_string);
                  goto usage_error;
                }
              printfmt (&errn,
                        2,
                        "output file argument: `%s'\n",
                        opt_arg);
              break;
            }
        }
      return 0;
    }

libhackerlab: The Hackerlab C Library
The Hackerlab at `regexps.com`