C++中对命令行参数的处理

背景介绍

首先说明这一是一篇大杂烩,没有什么技术点,重点只是对在常见的命令行参数解析库进行使用说明。更详细的官方都有文档,本文只是给一个方向和切入点,引导开拓一下思路。

一、命令行参数

命令行参数,这个很好理解,在使用操作系统或者说其它系统时,最典型的就是一个大黑屏,上面有跳跃的提示符,提示操作者输入相关命令,以Linux的终端为例,可以输入非常多的命令,举一个最常用的ls命令,它可以 列出当前文件夹下的相关文件和目录。有的时候儿为了更清楚一点可以使用“ls -al”命令,后面这个"-al"就是命令行参数。那么再使用帮助看一下另外一个常用命令ps:

ps --help all

用法:
 ps [选项]

基本选项:
 -A, -e               all processes
 -a                   all with tty, except session leaders
  a                   all with tty, including other users
 -d                   all except session leaders
 -N, --deselect       negate selection
......

原来在使用Go语言时,发现其中有两个开源的命令行参数分析框架,可以说相当的优秀(有兴趣可以看看以太坊中对命令行参数的处理,用的就是其中一个框架)。但是在C++中对这方面的经验相对比较少。缺啥补啥,这就是咱们的口号。
其实在C/C++也有不少这种框架,比较好的也有几个,这次介绍两个,一个是GNU的argp,一个是cargs。可能别的也有更好的,但都介绍介绍不过来,这里只介绍这两个,重点介绍前一个。

二、处理参数

其实在默认的C/C++函数中,在一个主函数中,可以是下面这样:

int main(int argc,char **argv){
  int cmd_count = argc;
  char *cmd0 = argv[0];
  char *cmd1 = argv[1];
...
}

例如输入:

./main arg1  arg2 arg3

上面假设代码编译出来的可执行文件为main,后面跟了三个参数。那么 cmd_count的值为4,cmd0为"./main",cmd1值为 arg1,依次类推。也就是说,这个参数中包含可执行程序本身,它为索引为0的参数值。
这样做简单方便,但在对于一些复杂的命令行参数时,特别是可以分组、多参协作等时,会有它有不足之处。说的直白一些,如果命令行很复杂,该怎么办?这就需要有专门的处理的方式或者库。正如前面提到的GO语言中的第三方框架。这里先分析一下GNU的argp。

1、argp
先看一段代码:

 #include 
 #include 
#include 

const char *argp_program_version =
  "argp-ex2 1.0";
const char *argp_program_bug_address =
  "";


static int parse_opt (int key, char *arg, struct argp_state *state)
{
    switch (key){
        case 'd': {
            unsigned int i;
            unsigned int num = 0;
            if (arg == NULL)
                num = 1;
            else
                num = atoi (arg);
            for (i = 0; i < num; i++)
                printf ("a"); 
            printf ("\n");
            break;
	    }
    }
  return 0;
}

int main (int argc, char **argv){
    struct argp_option options[] = {
        { "aprint", 'd', "NUM", OPTION_ARG_OPTIONAL, "Show print a"},
        { 0 }
    };
    
    struct argp argp = { options, parse_opt, 0, 0 };
    return argp_parse (&argp, argc, argv, 0, 0, 0);
}

这是一个很简单的代码,argp_program_version和argp_program_bug_address这两个全局变量是其自带的,只要设置了,就会出现版本信息和bug处理地址。而argp,是处理的核心,通过argp_parse这个函数设置处理的过程。也就是说,通过argp_parse设置后,即可不断的调用 argp内的函数指针parse_opt来处理传入的参数options并保存到指定的数据结构中。它的使用方式如下:

./testCmdparse -?
Usage: testCmdparse [OPTION...]

  -d, --aprint[=NUM]         Show print a
  -?, --help                 Give this help list
      --usage                Give a short usage message
  -V, --version              Print program version

Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.

Report bugs to .
/
./testCmdparse -d3
aaa

对argp_option和argp这两个结构体不明白的,可以看一下定义:

/* A description of a particular option.  A pointer to an array of
   these is passed in the OPTIONS field of an argp structure.  Each option
   entry can correspond to one long option and/or one short option; more
   names for the same option can be added by following an entry in an option
   array with options having the OPTION_ALIAS flag set.  */
struct argp_option
{
  /* The long option name.  For more than one name for the same option, you
     can use following options with the OPTION_ALIAS flag set.  */
  const char *name;

  /* What key is returned for this option.  If > 0 and printable, then it's
     also accepted as a short option.  */
  int key;

  /* If non-NULL, this is the name of the argument associated with this
     option, which is required unless the OPTION_ARG_OPTIONAL flag is set. */
  const char *arg;

  /* OPTION_ flags.  */
  int flags;

  /* The doc string for this option.  If both NAME and KEY are 0, This string
     will be printed outdented from the normal option column, making it
     useful as a group header (it will be the first thing printed in its
     group); in this usage, it's conventional to end the string with a `:'.  */
  const char *doc;

  /* The group this option is in.  In a long help message, options are sorted
     alphabetically within each group, and the groups presented in the order
     0, 1, 2, ..., n, -m, ..., -2, -1.  Every entry in an options array with
     if this field 0 will inherit the group number of the previous entry, or
     zero if it's the first one, unless its a group header (NAME and KEY both
     0), in which case, the previous entry + 1 is the default.  Automagic
     options such as --help are put into group -1.  */
  int group;
};
/* An argp structure contains a set of options declarations, a function to
   deal with parsing one, documentation string, a possible vector of child
   argp's, and perhaps a function to filter help output.  When actually
   parsing options, getopt is called with the union of all the argp
   structures chained together through their CHILD pointers, with conflicts
   being resolved in favor of the first occurrence in the chain.  */
struct argp
{
  /* An array of argp_option structures, terminated by an entry with both
     NAME and KEY having a value of 0.  */
  const struct argp_option *options;

  /* What to do with an option from this structure.  KEY is the key
     associated with the option, and ARG is any associated argument (NULL if
     none was supplied).  If KEY isn't understood, ARGP_ERR_UNKNOWN should be
     returned.  If a non-zero, non-ARGP_ERR_UNKNOWN value is returned, then
     parsing is stopped immediately, and that value is returned from
     argp_parse().  For special (non-user-supplied) values of KEY, see the
     ARGP_KEY_ definitions below.  */
  argp_parser_t parser;

  /* A string describing what other arguments are wanted by this program.  It
     is only used by argp_usage to print the `Usage:' message.  If it
     contains newlines, the strings separated by them are considered
     alternative usage patterns, and printed on separate lines (lines after
     the first are prefix by `  or: ' instead of `Usage:').  */
  const char *args_doc;

  /* If non-NULL, a string containing extra text to be printed before and
     after the options in a long help message (separated by a vertical tab
     `\v' character).  */
  const char *doc;

  /* A vector of argp_children structures, terminated by a member with a 0
     argp field, pointing to child argps should be parsed with this one.  Any
     conflicts are resolved in favor of this argp, or early argps in the
     CHILDREN list.  This field is useful if you use libraries that supply
     their own argp structure, which you want to use in conjunction with your
     own.  */
  const struct argp_child *children;

  /* If non-zero, this should be a function to filter the output of help
     messages.  KEY is either a key from an option, in which case TEXT is
     that option's help text, or a special key from the ARGP_KEY_HELP_
     defines, below, describing which other help text TEXT is.  The function
     should return either TEXT, if it should be used as-is, a replacement
     string, which should be malloced, and will be freed by argp, or NULL,
     meaning `print nothing'.  The value for TEXT is *after* any translation
     has been done, so if any of the replacement text also needs translation,
     that should be done by the filter function.  INPUT is either the input
     supplied to argp_parse, or NULL, if argp_help was called directly.  */
  char *(*help_filter) (int __key, const char *__text, void *__input);

  /* If non-zero the strings used in the argp library are translated using
     the domain described by this string.  Otherwise the currently installed
     default domain is used.  */
  const char *argp_domain;
};

再看一段代码:

 #include 
#include 

const char *argp_program_version =
  "argp-ex2 1.0";
const char *argp_program_bug_address =
  "";

/* Program documentation. */
static char doc[] =
  "Argp example #2 -- a pretty minimal program using argp";

/* Our argument parser.  The options, parser, and
   args_doc fields are zero because we have neither options or
   arguments; doc and argp_program_bug_address will be
   used in the output for ‘--help’, and the ‘--version’
   option will print out argp_program_version. */
static struct argp argp = { 0, 0, 0, doc };

int
main (int argc, char **argv)
{
  argp_parse (&argp, argc, argv, 0, 0, 0);
  exit (0);
}

这个主要是看看DOC的应用。还是比较简单的。

2、cargs
cargs需要在github上下载相关的库:

//https://github.com/likle/cargs
git clone -b stable [email protected]:likle/cargs.git

它的用法也比较简单,看下面的例程就会明白。

三、例程

1、看一个argp的官网的例子:

#include 
#include 
#include 

const char *argp_program_version =
  "argp-ex4 1.0";
const char *argp_program_bug_address =
  "";

/* Program documentation. */
static char doc[] =
  "Argp example #4 -- a program with somewhat more complicated\
options\
\vThis part of the documentation comes *after* the options;\
 note that the text is automatically filled, but it's possible\
 to force a line-break, e.g.\n<-- here.";

/* A description of the arguments we accept. */
static char args_doc[] = "ARG1 [STRING...]";

/* Keys for options without short-options. */
#define OPT_ABORT  1            /* –abort */

/* The options we understand. */
static struct argp_option options[] = {
  {"verbose",  'v', 0,       0, "Produce verbose output" },
  {"quiet",    'q', 0,       0, "Don't produce any output" },
  {"silent",   's', 0,       OPTION_ALIAS },
  {"output",   'o', "FILE",  0,
   "Output to FILE instead of standard output" },

  {0,0,0,0, "The following options should be grouped together:" },
  {"repeat",   'r', "COUNT", OPTION_ARG_OPTIONAL,
   "Repeat the output COUNT (default 10) times"},
  {"abort",    OPT_ABORT, 0, 0, "Abort before showing any output"},

  { 0 }
};

/* Used by main to communicate with parse_opt. */
struct arguments
{
  char *arg1;                   /* arg1 */
  char **strings;               /* [string…] */
  int silent, verbose, abort;   /* ‘-s’, ‘-v’, ‘--abort’ */
  char *output_file;            /* file arg to ‘--output’ */
  int repeat_count;             /* count arg to ‘--repeat’ */
};

/* Parse a single option. */
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
  /* Get the input argument from argp_parse, which we
     know is a pointer to our arguments structure. */
  struct arguments *arguments = state->input;

  switch (key)
    {
    case 'q': case 's':
      arguments->silent = 1;
      break;
    case 'v':
      arguments->verbose = 1;
      break;
    case 'o':
      arguments->output_file = arg;
      break;
    case 'r':
      arguments->repeat_count = arg ? atoi (arg) : 10;
      break;
    case OPT_ABORT:
      arguments->abort = 1;
      break;

    case ARGP_KEY_NO_ARGS:
      argp_usage (state);

    case ARGP_KEY_ARG:
      /* Here we know that state->arg_num == 0, since we
         force argument parsing to end before any more arguments can
         get here. */
      arguments->arg1 = arg;

      /* Now we consume all the rest of the arguments.
         state->next is the index in state->argv of the
         next argument to be parsed, which is the first string
         we’re interested in, so we can just use
         &state->argv[state->next] as the value for
         arguments->strings.

         In addition, by setting state->next to the end
         of the arguments, we can force argp to stop parsing here and
         return. */
      arguments->strings = &state->argv[state->next];
      state->next = state->argc;

      break;

    default:
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}

/* Our argp parser. */
static struct argp argp = { options, parse_opt, args_doc, doc };

int
main (int argc, char **argv)
{
  int i, j;
  struct arguments arguments;

  /* Default values. */
  arguments.silent = 0;
  arguments.verbose = 0;
  arguments.output_file = "-";
  arguments.repeat_count = 1;
  arguments.abort = 0;

  /* Parse our arguments; every option seen by parse_opt will be
     reflected in arguments. */
  argp_parse (&argp, argc, argv, 0, 0, &arguments);

  if (arguments.abort)
    error (10, 0, "ABORTED");

  for (i = 0; i < arguments.repeat_count; i++)
    {
      printf ("ARG1 = %s\n", arguments.arg1);
      printf ("STRINGS = ");
      for (j = 0; arguments.strings[j]; j++)
        printf (j == 0 ? "%s" : ", %s", arguments.strings[j]);
      printf ("\n");
      printf ("OUTPUT_FILE = %s\nVERBOSE = %s\nSILENT = %s\n",
              arguments.output_file,
              arguments.verbose ? "yes" : "no",
              arguments.silent ? "yes" : "no");
    }

  exit (0);
}

这个例子,相对来说已经比较复杂了。对着文档可以很清楚的知道它在做什么,这样就很容易将其为已所用。

2、carg的例子:

#include 
#include 
#include 

/**
* This is the main configuration of all options available.
*/
static struct cag_option options[] = {
 {.identifier = 's',
  .access_letters = "s",
  .access_name = NULL,
  .value_name = NULL,
  .description = "Simple flag"},

 {.identifier = 'm',
   .access_letters = "mMoO",
   .access_name = NULL,
   .value_name = NULL,
   .description = "Multiple access letters"},

 {.identifier = 'l',
   .access_letters = NULL,
   .access_name = "long",
   .value_name = NULL,
   .description = "Long parameter name"},

 {.identifier = 'k',
   .access_letters = "k",
   .access_name = "key",
   .value_name = "VALUE",
   .description = "Parameter value"},

 {.identifier = 'h',
   .access_letters = "h",
   .access_name = "help",
   .description = "Shows the command help"}};

/**
* This is a custom project configuration structure where you can store the
* parsed information.
*/
struct demo_configuration
{
 bool simple_flag;
 bool multiple_flag;
 bool long_flag;
 const char *key;
};

int main(int argc, char *argv[])
{
 char identifier;
 const char *value;
 cag_option_context context;
 struct demo_configuration config = {false, false, false, NULL};
 int param_index;

 /**
  * Now we just prepare the context and iterate over all options. Simple!
  */
 cag_option_prepare(&context, options, CAG_ARRAY_SIZE(options), argc, argv);
 while (cag_option_fetch(&context)) {
   identifier = cag_option_get(&context);
   switch (identifier) {
   case 's':
     config.simple_flag = true;
     break;
   case 'm':
     config.multiple_flag = true;
     break;
   case 'l':
     config.long_flag = true;
     break;
   case 'k':
     value = cag_option_get_value(&context);
     config.key = value;
     break;
   case 'h':
     printf("Usage: cargsdemo [OPTION]...\n");
     printf("Demonstrates the cargs library.\n\n");
     cag_option_print(options, CAG_ARRAY_SIZE(options), stdout);
     printf("\nNote that all formatting is done by cargs.\n");
     return EXIT_SUCCESS;
   }
 }

 printf("simple_flag: %i, multiple_flag: %i, long_flag: %i, key: %s\n",
   config.simple_flag, config.multiple_flag, config.long_flag,
   config.key ? config.key : "-");

 for (param_index = context.index; param_index < argc; ++param_index) {
   printf("additional parameter: %s\n", argv[param_index]);
 }

 return EXIT_SUCCESS;
}

执行:

~$ ./cargsdemo -k=test -sm --long
simple_flag: 1, multiple_flag: 1, long_flag: 1, key: test


~$ ./cargsdemo --help
Usage: cargsdemo [OPTION]...
Demonstrates the cargs library.

 -s                   Simple flag
 -m, -M, -o, -O       Multiple access letters
 --long               Long parameter name
 -k, --key=VALUE      Parameter value
 -h, --help           Shows the command help

Note that all formatting is done by cargs.


~$ ./cargsdemo also -k=test some -sm additional --long parameters
simple_flag: 1, multiple_flag: 1, long_flag: 1, key: test
additional parameter: also
additional parameter: some
additional parameter: additional
additional parameter: parameters

源码之前,了无秘密,再有文档辅助,这种硬格式的代码,非常容易理解。
除了上面的两个库还有一个argparse这个也库也不错,只包含一个头文件就可以使用。github上有不少类似的库,可以好好看看,开拓一下思路和视野。

四、总结

很多经验告诉我们,做一件事,单纯的按规划来,其实非常简单。但实际情况是,很多异常和特殊情况需要处理,这就极大的增加了复杂度。就如上面的命令行,如果大家只是写程序只带一两个参数,那么就没必要用这些库,也就没人写。可实际上呢?这种需求是现实的而且广泛的,能不用么?
还是那句话,学习别人的框架和库,要善于为已所用。在此基础上,要消化吸收,学习它的精髓即思想和架构。

你可能感兴趣的:(C++,c++)