处理命令行参数的设计模式是什么?

51

如果您正在编写一个可从命令行执行的程序,通常希望为用户提供几个选项或标志,以及可能超过一个参数。我经常摸索着完成这个任务,但是否有一种设计模式可以循环处理参数并调用适当的处理函数?

考虑:

myprogram -f filename -d directory -r regex
您如何在使用语言内置函数检索参数后组织处理程序函数?(如果这有助于您表达答案,欢迎提供特定于语言的答案)

使用所用编程语言的内置函数获取参数后,您可以如何组织处理程序函数呢?(如果有需要,欢迎提供特定于语言的答案)


你编写了一个程序,需要用户输入位置或关键字以及必需或可选参数。然后,您的程序需要对这些提交的参数的某些组合/排列进行操作。您是在询问有关结构化函数的设计模式,这些函数根据给定的输入执行不同的任务吗?还是您正在询问循环遍历和解析参数的设计模式(例如,您正在询问由解析器库实现的设计模式,如C的getopt和Python的argparse)? - Minh Tran
15个回答

19

我认为以下答案更符合你的要求:

你应该考虑应用模板模式(《设计模式》[Gamma,等人]中的模板方法)

简而言之,它的整体处理过程如下:

If the arguments to the program are valid then
    Do necessary pre-processing
    For every line in the input
        Do necessary input processing
    Do necessary post-processing
Otherwise
    Show the user a friendly usage message

简而言之,实现一个ConsoleEngineBase类,其中包含以下方法:

PreProcess()
ProcessLine()
PostProcess()
Usage()
Main()
然后创建一个底盘(chassis),实例化ConsoleEngine()并发送Main()消息来启动它。要查看如何将其应用于控制台或命令行程序的好示例,请查看以下链接:http://msdn.microsoft.com/en-us/magazine/cc164014.aspx。示例是用C#编写的,但是这些思想可以很容易地应用于任何其他环境中。您可以将GetOpt()视为适合参数处理(预处理)的部分。希望这可以帮助你。

2
赞成坚持概念而非实现方式。我觉得这应该是被选中的答案。 - Plasmarob

14

我不知道有关处理方案的任何记录。

我相信处理参数的最古老的库/ API之一是 getopt。谷歌“getopt”会显示很多手册和实现链接。

通常,在我的应用程序中,我会有一个偏好或设置服务,参数处理器知道如何与其通信。然后将参数转换为此服务中的某些内容,应用程序可以查询。这可能只是一个设置字典(例如称为“filename”的字符串设置)。


6

您没有提到具体的语言,但对于Java,我们非常喜欢使用Apache Commons CLI。对于C/C++,则是使用getopt。


5

这是一篇旧文章,但我仍然想做出贡献。问题是关于选择设计模式,然而我看到了很多关于使用哪个库的讨论。我已经查看了 Lindsay 提供的链接,该链接讲解了要使用模板设计模式。

然而,我对这篇文章并不满意。模板模式的目的是定义一个模板,然后由各种其他类实现以获得统一的行为。我认为这不适用于解析命令行。

我更倾向于使用 "Command" 设计模式。这个模式最适合菜单驱动选项。

http://www.blackwasp.co.uk/Command.aspx

因此,在您的情况下,-f、-d 和 -r 都成为命令,具有共同或单独的接收器定义。这样就可以在未来定义更多的接收器。下一步将是链接这些命令的责任,以防需要处理链。对此,我会选择。

http://www.blackwasp.co.uk/ChainOfResponsibility.aspx

我想这两者的组合是最好的方式来组织命令行处理代码或任何菜单驱动方法。


“-f,-d,-r”似乎指定了单个“ConcreteCommand”的信息。如果用户指定了其他组合的标志“-f -d”,例如(或仅为“-f”或仅为“-r”或仅为“-d”),则每个标志都可以表示另一个“ConcreteCommand”,其功能(由“Execute()”实现)与使用“-f,-d,-r”初始化的“ConcreteCommand”不同。 - Minh Tran

4
关于这个问题,我有几点评论。
首先,虽然没有什么固定的模式,但编写解析器本质上是一个机械化的过程,因为在给定语法的情况下,可以很容易地生成解析器。例如,可以使用Bison和ANTLR等工具。
尽管如此,解析器生成器通常对于命令行来说有些大材小用。因此,通常的做法是自己编写一次(正如其他人所演示的那样),直到你厌倦了处理繁琐的细节并找到一个库来代替你编写。
我为C++编写了一个库,它可以节省大量的工作量,使 getopt 代码更加简洁,并且还可以很好地利用模板:TCLAP

TCLAP 是一个非常棒的命令行解析库。预解析规则的设置和 argv 解析后的提取非常有用、直观,并且有助于将程序拆分为正确的离散组件(在我看来)。 - Sean

2

假设您有一个“config”对象,您希望使用标志设置它,并且具有适当的命令行解析器,负责解析命令行并提供选项的常量流,则以下是一段伪代码:

while (current_argument = cli_parser_next()) {
    switch(current_argument) {
        case "f": //Parser strips the dashes
        case "force":
            config->force = true;
            break;
        case "d":
        case "delete":
            config->delete = true;
            break;
        //So on and so forth
        default:
            printUsage();
            exit;
    }
}

2

我更喜欢使用 "-t text" 和 "-i 44" 这样的选项;我不喜欢 "-fname" 或 "--very-long-argument=some_value"。

"-?"、"-h" 和 "/h" 都能显示帮助信息。

以下是我的代码外观:

int main (int argc, char *argv[])
   {  int i;
      char *Arg;
      int ParamX, ParamY;
      char *Text, *Primary;

   // Initialize...
   ParamX = 1;
   ParamY = 0;
   Text = NULL;
   Primary = NULL;

   // For each argument...
   for (i = 0; i < argc; i++)
      {
      // Get the next argument and see what it is
      Arg = argv[i];
      switch (Arg[0])
         {
         case '-':
         case '/':
            // It's an argument; which one?
            switch (Arg[1])
               {
               case '?':
               case 'h':
               case 'H':
                  // A cry for help
                  printf ("Usage:  whatever...\n\n");
                  return (0);
                  break;

               case 't':
               case 'T':
                  // Param T requires a value; is it there?
                  i++;
                  if (i >= argc)
                     {
                     printf ("Error:  missing value after '%s'.\n\n", Arg);
                     return (1);
                     }

                  // Just remember this
                  Text = Arg;

                  break;

               case 'x':
               case 'X':
                  // Param X requires a value; is it there?
                  i++;
                  if (i >= argc)
                     {
                     printf ("Error:  missing value after '%s'.\n\n", Arg);
                     return (1);
                     }

                  // The value is there; get it and convert it to an int (1..10)
                  Arg = argv[i];
                  ParamX = atoi (Arg);
                  if ((ParamX == 0) || (ParamX > 10))
                     {
                     printf ("Error:  invalid value for '%s'; must be between 1 and 10.\n\n", Arg);
                     return (1);
                     }

                  break;

               case 'y':
               case 'Y':
                  // Param Y doesn't expect a value after it
                  ParamY = 1;
                  break;

               default:
                  // Unexpected argument
                  printf ("Error:  unexpected parameter '%s'; type 'command -?' for help.\n\n", Arg);
                  return (1);
                  break;
               }

            break;

         default:
            // It's not a switch that begins with '-' or '/', so it's the primary option
            Primary = Arg;

            break;
         }
      }

   // Done
   return (0);
   }

2
我参考了mes5k的ANTLR答案。这个链接是一篇关于ANTLR和使用访问模式实现你的应用程序所需操作的文章。它写得很好,值得一读。 Codeproject链接

2

这是一个不错的选择。与旧的 getopt 相比,该库有点复杂,但也允许使用配置文件(替换或集成命令行参数)。 - Antonio Sesto


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接