2.8 Parsing the Environment

In addition to parsing options, mu_parse_opts supports parsing environment variables as well. Environment variables are specified using the env_var field (see Option Structure). Values of environment variables are specified in the same way as arguments are specified to options (see Option Arguments).

Unlike options, environment variables are parsed in the program environment (or the environment parameter to mu_opt_context_new_with_env), rather than in argv (see Parsing Options and Environment). And unlike long options and suboptions, environment variables may not be abbreviated. And whereas an invalid option will cause an error, an invalid environment variable will be ignored.

Environment variables may be specified for suboptions, and an environment variable may take suboptions as a value as well. For example, you might have an environment variable, ENV, which takes a suboption foo, which itself takes an optional string argument, say. And suppose foo has an equivalent environment variable, ENV_FOO. Then you might specify a value ‘bar’ to the foo suboption either by specifying a value to ENV like this: ENV=foo=bar, or by specifying a value directly to ENV_FOO like this: ENV_FOO=bar. The example shows how to do this as well. See Parsing Suboptions for more information on suboptions.

Environment variables are always parsed before command line options. Environment variables and long/short options may be specified in the same option, but if this is the case, the command line option(s) will take precedence over the environment variable, since the environment variables will always be parsed first.

If an environment variable has aliases (see Aliases for Options and Environment Variables), aliases specified first will take precedence. For example, if an environment variable is specified as ‘FOO|BAR’, and both FOO and BAR are in the environment, then the value of FOO will take precedence because it was specified as an alias before BAR. Note also that if both FOO and BAR are specified in the environment, the value of BAR will be completely ignored. The callback (if any) will only be called once, for FOO (see Option Callbacks).

Another thing to note is that if you have an environment variable with a has_arg value of MU_OPT_NONE, then if that environment variable is encountered, and it has a value other than the empty string, that will cause an error. This is not very user-friendly behavior, and you might consider using a has_arg of MU_OPT_OPTIONAL and an arg_type of MU_OPT_BOOL. Then, if the environment variable has no value, you can default to true. This is more user-friendly, because things like ENV_VAR=yes or ENV_VAR=no will do what is expected (assuming your environment variable is called ENV_VAR).

Traditionally, environment variable names are in ALL CAPS.

Here is an example of how environment variables can be parsed:

#include <stdio.h>
#include <mu/options.h>
#include <mu/safe.h>            /* For mu_opt_context_x* */

/* Print a message when an option is found. */
int print_opt(int has_arg, const char *arg,
              void *data, char *err) {
  const char *name = data;
  printf("Found an option/environment variable '%s'", name);
  if (has_arg)
    printf(" with an argument '%s'", arg);
  putchar('\n');
  return 0;
}

int main(int argc, char **argv) {
  int ret;
  const MU_OPT suboptions[] = {
    {
     .subopt_name     = "subopt",
     /* Suboptions can have environment variables as well. */
     .env_var         = "ENV_SUBOPT",
     .has_arg         = MU_OPT_OPTIONAL,
     .arg_type        = MU_OPT_STRING,
     .callback_string = print_opt,
     .cb_data         = "a suboption",
     .help            =
     "a suboption with an equivalent environment variable"
    },
    { 0 }
  };
  const MU_OPT options[] = {
    {
     .short_opt       = "a",
     .long_opt        = "an-option",
     /* AN_ENV_VAR will always take precedence over ALIAS since it is
        specified first below. */
     .env_var         = "AN_ENV_VAR|ALIAS",
     .has_arg         = MU_OPT_OPTIONAL,
     .arg_type        = MU_OPT_STRING,
     .callback_string = print_opt,
     .cb_data         = "an option",
     .help            =
     "an option with an equivalent environment variable"
    },
    {
     .short_opt       = "b",
     .long_opt        = "another-option",
     .has_arg         = MU_OPT_OPTIONAL,
     .arg_type        = MU_OPT_STRING,
     .callback_string = print_opt,
     .cb_data         = "another option",
     .help            =
     "an option without an equivalent environment variable"
    },
    {
     .env_var         = "ANOTHER_ENV_VAR",
     .has_arg         = MU_OPT_REQUIRED,
     /* Environment variables can have suboptions as well. */
     .arg_type        = MU_OPT_SUBOPT,
     .subopts         = suboptions,
     .help            =
     "an environment variable (which takes "
     "suboptions) without an equivalent option"
    },
    { 0 }
  };
  MU_OPT_CONTEXT *context;

  context = mu_opt_context_xnew(argc, argv, options, MU_OPT_PERMUTE);

  /* Add the help option. */
  mu_opt_context_add_help(context, NULL, NULL,
                          "Parse options and environment variables.",
                          NULL, "1", NULL, NULL, NULL);
  mu_opt_context_xadd_help_options(context, MU_HELP_BOTH);

  /* Parse the options. */
  ret = mu_parse_opts(context);
  mu_opt_context_xfree(context);
  if (MU_OPT_ERR(ret))
    return 1;

  return 0;
}

And here is the output of the above program (note, the COLUMNS environment variable is set to 65 so that the help message will look good in this manual):

$ COLUMNS=65
$ export COLUMNS
$ AN_ENV_VAR=foo ./environ --an-option=bar --another-option=baz
-| Found an option/environment variable 'an option' with an argument 'foo'
-| Found an option/environment variable 'an option' with an argument 'bar'
-| Found an option/environment variable 'another option' with an argument 'baz'
$ ANOTHER_ENV_VAR=subopt=foo ./environ
-| Found an option/environment variable 'a suboption' with an argument 'foo'
$ ENV_SUBOPT=foo ./environ
-| Found an option/environment variable 'a suboption' with an argument 'foo'
# AN_ENV_VAR will always take precedence over ALIAS. Also note that
# the callback is only called once, even though both aliases are
# specified.
$ AN_ENV_VAR=foo ALIAS=bar ./environ
-| Found an option/environment variable 'an option' with an argument 'foo'
$ ALIAS=bar AN_ENV_VAR=foo ./environ
-| Found an option/environment variable 'an option' with an argument 'foo'
$ ./environ --help
-| Usage: ./environ [OPTION]...
-| Parse options and environment variables.
-|  
-|   -a, --an-option[=STRING]       an option with an equivalent environment variable
-|   -b, --another-option[=STRING]  an option without an equivalent environment variable
-|   -h, --help[=plain|man]         print this help in plain text format if 'plain', or as a man(1)
-|                                    page if 'man'; if the argument is omitted, it will default to
-|                                    'plain'.
-|  
-| Suboptions for ANOTHER_ENV_VAR:
-|   subopt[=STRING]                a suboption with an equivalent environment variable
-|  
-| ENVIRONMENT
-|  
-|   AN_ENV_VAR, ALIAS[=STRING]     an option with an equivalent environment variable
-|   ANOTHER_ENV_VAR=SUBOPTS        an environment variable (which takes suboptions) without an
-|                                    equivalent option
-|   ENV_SUBOPT[=STRING]            a suboption with an equivalent environment variable