65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的自动命令行解析

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (68投票s)

2003 年 3 月 25 日

CPOL

7分钟阅读

viewsIcon

390821

downloadIcon

3309

一个允许自动命令行开关和解析的实用类。

Sample Image - CommandLineParser.png

引言

在我尝试用一种新的编程语言做任何事情时,我倾向于写的第一个东西就是控制台测试应用程序。这些测试应用程序往往会演变成完整的测试平台——很快就会超出最初的功能。通常,它们还会开始处理我在开发过程中发现的所有特殊情况。

最终,我不得不在命令行中提供开关来更改应用程序的功能。我一直想要的是一个智能的命令行解析器,以最大限度地减少添加此类功能所需的编码量。

本文展示了我目前(仍在开发中)的命令行解析器,它基于 .net 的属性,并使用正则表达式来分割各个部分。

在本文的最后,我包含了一个(给我自己看的)愿望清单,并将继续开发这个类。更新和更改列表将添加到原始文章中。

读取命令行

正如大多数开发人员所知,查看运行应用程序的命令行参数有许多方法。两种典型的方法是:

  • Main 函数接受一个字符串数组,该数组代表每个由空格分隔的参数(或正确引用时包含空格的参数)。

    public static int Main( string[] cmdLine )
    {
        Console.WriteLine("Command-line strings are:");
        foreach( string s in cmdLine )
            Console.WriteLine(s);
        return 0;
    }
    
  • 第二种方法涉及查看环境命令行。

    public static int Main( string[] cmdLine )
    {
        // Ignore the cmdLine passed in above....
        Console.WriteLine("Command-line is: {0}",
            System.Environment.CommandLine);
        return 0;
    }

以上两种方法在传递命令行参数时都有优缺点。第一种方法很容易遍历,例如,当提供了单个文件名时。后一种方法允许解析复杂的开关,例如接受可选参数的开关。

在大多数情况下,程序员需要遍历命令行开关,查找与其预期开关相匹配的文本。这通常会使代码看起来像一系列大的 if 语句或一个巨大的 switch 语句。

我的目的是从我的测试应用程序中消除这种混乱和不断重写相似代码的情况,事实上,现在只需要很少的编码。

注册开关

解析器类有两种不同的方式来了解命令行开关。编程添加的开关或自动添加的开关。

编程开关

这是将某项添加到类中的典型方式。在构造解析器类之后(但在运行解析器之前),可以添加开关。目前,这通过调用成员函数(注意下面示例中的重载)AddSwitch 来实现。

Parser parser = new Parser( System.Environment.CommandLine);

// Programmatically add some switches to the command line parser.
parser.AddSwitch( "Wibble", "Do something silly" );

// Add a switches with aliases for the first name, "help" and "a".
parser.AddSwitch(new string[] { "help", @"\?" }, "show help");
parser.AddSwitch(new string[] { "a", "b", "c", "d", "e", "f" }, "Early alphabet");

// Parse the command line.
parser.Parse();

在上面的示例中,有两种添加开关别名的方法。例如,“help”可以由 help 或 /? 来处理。之所以引用问号,是因为它们底层使用了正则表达式。有必要以这种方式引用字符串,以防止在正则表达式处理程序中引发异常。

注意:目前,只能注册布尔类型的开关。AddSwitch 函数很可能会在下一个版本中更改,以接受类型参数。

解析器执行后,可以通过调用以下方法查看开关是否已设置:

if ( parser["help"] != null )
  Console.WriteLine("Request for help = {0}", parser["help"]);
else
  Console.WriteLine("Request for help has no associated value.");

自动开关

利用 C# 和 .net 中提供的出色的属性和反射机制,可以使上述代码更加简洁。考虑一下,当你开发一个类时,你通常会为你的应用程序的不同功能创建属性。例如,让我们使用一个布尔值来指定用户是否希望看到一些帮助。

public class Application
{
    private bool m_ShowHelp = false;
    public bool ShowHelp
    {
        get { return m_ShowHelp; }
        set { m_ShowHelp = value; }
    }

    // ... other code.
}

与其添加我们通常会做的编程代码,不如考虑以下改编:

public class Application
{
    private bool m_ShowHelp = false;

    [CommandLineSwitch("help","Show the help")]
    public bool ShowHelp
    {
        get { return m_ShowHelp; }
        set { m_ShowHelp = value; }
    }

    // ... other code.
}

分配给属性的 get/set 方法的 CommandLineSwitchAttribute 属性(在使用时可以缩写为 CommandLineSwitch),表示 ShowHelp 是一个名为“help”的命令行开关。我们唯一需要做的不同之处是将类的实例传递给解析器的构造函数。

public class Application
{
    private bool m_ShowHelp = false;

    [CommandLineSwitch("help","Show the help")]
    public bool ShowHelp
    {
        get { return m_ShowHelp; }
        set { m_ShowHelp = value; }
    }

    // ... other code.

    private void Run()
    {
        Console.WriteLine("Show help defaults to FALSE. " + 
                          "Currently it is : " + ShowHelp);

        // Add a reference to this class (instance needed) to the parser.
        Parser parser = new Parser( System.Environment.CommandLine, this );
        parser.Parse();

        Console.WriteLine("The current value of ShowHelp : " + ShowHelp);
    }

    public static int Main( string[] cmdLine );
    {
        Application a = new Application();
        a.Run();
        return 0;
    }
}

解析器类现在将遍历 Application 类(作为 Parser 类构造函数中的第二个参数传递)的方法和属性,查找任何 CommandLineSwitchAttributes。在解析命令行时找到匹配项的情况下,ShowHelp 的内部值将被直接修改,而无需查看解析器的结果。

C:\> test
Show help defaults to FALSE.  Currently it is : false
The current value of ShowHelp : false

C:\> test /help
Show help defaults to FALSE.  Currently it is : false
The current value of ShowHelp : true

C:\> test --help
Show help defaults to FALSE.  Currently it is : false
The current value of ShowHelp : true

开关

解析器目前处理三种类型的开关(注意:以编程方式添加的开关目前仅支持布尔类型)。

  • 布尔值
  • 字符串
  • 整数
  • 枚举 (** 新 **)

使用开关

所有开关都可以与以下任何转义符一起使用,请参见下面的“help”开关示例。目前,这些类型可以在同一个命令行中混合使用。

  • /help 单个正斜杠
  • -help 单个连字符
  • --help 双连字符

布尔开关

这些开关是最基本的,由编程和自动机制支持。开关的出现会切换其状态(这对自动开关很重要,因为它们可能默认为 true)。继续以“help”为例,可以给出以下开关指令:

  • /help 切换状态。当以编程方式注册时,这将把状态设置为 true。
  • /help+ 将布尔值设置为 true(无论其旧状态如何)
  • /help- 将布尔值设置为 false(无论其旧状态如何)

请注意,单个和双连字符前缀仍然有效。因此 --help+ 与 -help+ 相同。

字符串开关

目前,字符串类型开关仅通过自动注册的开关实现。

要引入一个新的示例开关,例如,我们在应用程序中定义了一个用户名:

private string m_UserName = "Unknown";

[CommandLineSwitch("user","Set the user name")]
public bool UserName
{
    get { return m_UserName; }
    set { m_UserName = value; }
}

我们可以通过以下方式之一提供新的用户名给应用程序:

  • /user:new-user-name
  • /user new-user-name
  • /user:"user name with spaces"
  • /user "user name with spaces"

请注意,单个和双连字符前缀仍然有效。因此 --user:fred + 与 -user:fred 相同

枚举类型开关 (** 新 **)

假设你有一个枚举类型用于一周中的某几天,你可能会实现类似以下的内容:

public enum DaysOfWeek 
{ 
   Sun, 
   Mon, 
   Tue, 
   Wed, 
   Thu, 
   Fri, 
   Sat 
};

如果需要命令行开关使用此枚举,现在已支持。通过在枚举上放置属性类型,命令行将能够接受枚举(类似于它接受字符串的方式)。请考虑以下几点:

[CommandLineSwitch("Day","Day of the week")]

public DaysOfWeek DoW { ... }

因此,以下内容在命令行中将是合法的:

  • /day:Mon
  • /day:tue (注意它不区分大小写)
  • /day sun

以下是开关的非法用法:

  • /day tomorrow
  • /day:thur (因为它应该是 thu)

如果为属性提供了 Set 方法,该值将被正确设置为相应的值。

访问命令行的其余部分

在所有命令行开关之后,如何访问非开关参数?好吧,没有什么太复杂的。在所有开关都从命令行中剥离之后,剩余的部分(由解析器)会被分割成由空格分隔(或带引号)的字符串,这些字符串由 Parameters 属性返回。

Console.WriteLine("Non-switch Params : {0}", parser.Parameters.Length);
for ( int j=0; j<parser.Parameters.Length; j++ )
    Console.WriteLine("{0} : {1}",j,parser.Parameters[j]);

愿望清单

下面的列表基本上是我的愿望清单,如果你想到了什么并告诉我,我可能会把你想要的也加进去。:-)

  • 扩展关于解析器中使用正则表达式的文章。
  • 扩展关于属性使用方式的文章。
  • 完成源代码的注释。
  • 添加更多开关类型。
  • 重新实现 AddSwitch 函数以接受所有开关类型。
  • 允许解析开关文件(以及命令行),例如,如果文件 config 包含运行时应用程序使用的开关(不是新开关,而是现有开关的设置),那么在命令行上放置 @config 将也会包含这些设置。

Licence

我不想在许可证方面过于严苛,所以就这样简单。我保留代码的版权,我对其适用性不提供任何保证。如果您将此实现或其衍生版本包含在您的代码/文章中,请将我视为原始作者。如果您将其包含在商业软件中,请在您的关于框/帮助信息中注明我的名字和对 codeproject 的引用。

哦,对了……给我发封电子邮件或在下方附上评论,说明你在哪里使用它。

历史 

日期 版本 更改说明
2003 年 3 月 25 日 0.1 首次公开发布
2003 年 3 月 26 日 0.2
  • 文章更新。
  • 代码更新以支持使用枚举类型。
  • 添加实用函数以检索解析后的开关,用于错误处理。
© . All rights reserved.