在C#中解析命令行参数的最佳方法是什么?

731

在构建需要传递参数的控制台应用程序时,您可以使用传递给Main(string[] args)的参数。

过去,我通常会对该数组进行索引/循环并执行一些正则表达式来提取值。 然而,当命令变得更加复杂时,解析可能会变得相当丑陋。

因此,我对以下内容感兴趣:

  • 您使用的库
  • 您使用的模式

假设命令始终遵循这里的公共标准。


之前的讨论 split-string-containing-command-line-parameters-into-string-in-c# 可能会有一些答案。 - gimel
1
抱歉,这有点离题。然而我确实使用“应用程序设置”来传递参数给应用程序。我发现它非常容易使用,无需编写参数/文件解析,也不需要额外的库。http://msdn.microsoft.com/en-us/library/aa730869(VS.80).aspx - call me Steve
44
@call me Steve:命令行参数的重点在于它们可以根据每次调用而变化 - 如何在应用设置中实现这一点? - reinierpost
20个回答

324

我强烈建议使用NDesk.Options文档)和/或Mono.Options(与API相同,但属于不同的命名空间)。以下是来自文档的示例:

bool show_help = false;
List<string> names = new List<string> ();
int repeat = 1;

var p = new OptionSet () {
    { "n|name=", "the {NAME} of someone to greet.",
       v => names.Add (v) },
    { "r|repeat=", 
       "the number of {TIMES} to repeat the greeting.\n" + 
          "this must be an integer.",
        (int v) => repeat = v },
    { "v", "increase debug message verbosity",
       v => { if (v != null) ++verbosity; } },
    { "h|help",  "show this message and exit", 
       v => show_help = v != null },
};

List<string> extra;
try {
    extra = p.Parse (args);
}
catch (OptionException e) {
    Console.Write ("greet: ");
    Console.WriteLine (e.Message);
    Console.WriteLine ("Try `greet --help' for more information.");
    return;
}

14
NDesk.Options很棒,但似乎不太支持具有多个不同命令的控制台应用程序。如果需要这方面的功能,请尝试ManyConsole,它是基于NDesk.Options构建的:http://nuget.org/List/Packages/ManyConsole - Frank Schwieterman
5
当我有一个包含多个不同命令的应用程序时,我会对OptionSets进行"分层"。以mdoc为例(http://docs.go-mono.com/index.aspx?link=man%3amdoc%281%29),它有一个“全局”OptionSet(https://github.com/mono/mono/blob/master/mcs/tools/mdoc/Mono.Documentation/mdoc.cs#L54),该OptionSet委托给每个命令的OptionSet(例如 https://github.com/mono/mono/blob/master/mcs/tools/mdoc/Mono.Documentation/monodocer.cs#L66)。 - jonp
3
NDesk对我来说无法工作。可以正常读取整数参数,但无法读取字符串。变量一直获取参数(例如's'、'a'等),而不是参数值(例如'serverName'、'ApplicationName')。放弃使用NDesk,并改用“命令行解析器库”。目前为止一切都好。 - Jay
2
@AshleyHenderson 首先,它很小巧灵活。大多数解决方案仅适用于可选命名参数(即不能像 git checkout master 这样操作),或者它们的参数不够灵活(即不支持 --foo 123 = --foo=123 = -f 123= -f=123-v -h = -vh)。 - Wernight
1
@FrankSchwieterman 那应该是一个独立的答案。感谢您的提示,ManyConsole真是太好了,非常适合我。 - quentin-starin
显示剩余4条评论

197

我真的很喜欢命令行解析器库(http://commandline.codeplex.com/)。它通过属性的方式提供了一种非常简单而优雅的设置参数的方法:

class Options
{
    [Option("i", "input", Required = true, HelpText = "Input file to read.")]
    public string InputFile { get; set; }

    [Option(null, "length", HelpText = "The maximum number of bytes to process.")]
    public int MaximumLenght { get; set; }

    [Option("v", null, HelpText = "Print details during execution.")]
    public bool Verbose { get; set; }

    [HelpOption(HelpText = "Display this help screen.")]
    public string GetUsage()
    {
        var usage = new StringBuilder();
        usage.AppendLine("Quickstart Application 1.0");
        usage.AppendLine("Read user manual for usage instructions...");
        return usage.ToString();
    }
}

6
这也是我最终选择的图书馆。我正在为一家大公司编写应用程序,这些应用程序需要在很多年后继续维护--自2005年以来,该库一直在不断更新,似乎很受欢迎,由C#社区活跃的人编写,并以BSD风格许可证授权,以防支持消失。 - Charles Burns
我也推荐这个。 我的唯一问题是: 指定允许的参数组合(例如,如果有移动参数,则还必须有源和目标参数)可能可以通过属性完成。但最好使用单独的参数验证逻辑来解决问题。 - Frames Catherine White
1
我喜欢Options类。它似乎支持无名参数和标志,比如--recursive - Wernight
2
我刚刚测试了它,并且在几分钟内为我的应用程序实现了选项。这是一个非常简单易用的库。 - Trismegistos
3
我发现这个库对我来说非常受限制。如果需要独占集合,你无法为每个集合定义所需的选项,因此必须手动检查它们。你也无法为未命名值定义最小要求,也必须手动检查它们。帮助屏幕生成器也不够灵活。如果该库的行为不能满足你的需求,你几乎无法改变它。 - Sergey Kostrukov

50

WPF TestApi库 自带了一款最好的C#命令行解析器。我强烈建议查看Ivo Manolov关于该API的博客

// EXAMPLE #2:
// Sample for parsing the following command-line:
// Test.exe /verbose /runId=10
// This sample declares a class in which the strongly-
// typed arguments are populated
public class CommandLineArguments
{
   bool? Verbose { get; set; }
   int? RunId { get; set; }
}

CommandLineArguments a = new CommandLineArguments();
CommandLineParser.ParseArguments(args, a);

19
命令行解析应该由供应商(例如Microsoft)提供,而不是通过第三方工具提供支持,即使供应商的支持方式可能有些迂回。 - Joel Coehoorn
2
话虽如此,被认可的答案(单声道)是下一个最好的选择。 - Joel Coehoorn
6
@Joel,哪个部分非常重要以至于命令行解析必须来自供应商?你的理由是什么? - greenoldman
3
@marcias: 我想他的意思是这个应该是“开箱即用”的...就像很多其他东西一样 :) - user7116
这个程序可以处理参数值中的空格吗?我刚刚得到它,连接起来了,但是不知道如何处理空格。例如:/MyValue="The Brown Fox"(我在第一次尝试时加上了引号)。 - granadaCoder
显示剩余2条评论

24

2
NDesk选项具有非常好的API。 - user35149
2
我会为NDesk再加一票,它运行良好,不会干扰,文档也很完善。 - Terence
1
Mono.GetOptions已经非常老旧了,NDesk.Options要好得多(或者如果你喜欢的话,可以使用Mono.Options,它是同一个类,在这里:http://anonsvn.mono-project.com/source/trunk/mcs/class/Mono.Options/Mono.Options/Options.cs) - Matt Enright
@Matt,是的,没错。我的问题也很旧了。已经一年零一周了 :) - abatishchev
7
@Adam Oren: 我的回答已经是1年零1个月前了!mono主干的结构已经进行了重构。那段代码现在被放置在http://anonsvn.mono-project.com/viewvc/branches/mono-2-2/mcs/class/Mono.Options/上。 - abatishchev
6
@Tormod说的是Mono.GetOptions已经过时了,而不是Mono.Options。Mono.Options仍然得到维护。 - jonp

14

看起来每个人都有自己的命令行解析器,我也想添加自己的解析器 :).

http://bizark.codeplex.com/

这个库包含一个命令行解析器,可以使用命令行中的值来初始化某个类。它具有许多功能(我已经建立了很多年)。

文档中可知...

BizArk框架中的命令行解析具有以下关键特点:

  • 自动初始化:基于命令行参数自动设置类属性。
  • 默认属性:发送一个值而不指定属性名称。
  • 值转换:使用BizArk中也包括的强大ConvertEx类将值转换为相应类型。
  • 布尔标志:可以通过简单使用参数(例如,/b表示true,/b-表示false)或添加true/false、yes/no等值来指定标志。
  • 参数数组:只需在命令行名称后添加多个值即可将定义为整数数组的属性设置为x,如/x 1 2 3(假设x被定义为整数数组)。
  • 命令行别名:一个属性可以支持多个命令行别名。例如,帮助使用别名?
  • 部分名称识别:您无需拼写完整的名称或别名,只需拼写足够使解析器区分该属性/别名即可。
  • 支持 ClickOnce: 可以在使用 ClickOnce 部署的应用程序中将属性初始化为查询字符串时进行初始化。命令行初始化方法将检测是否运行为 ClickOnce,因此您的代码在使用它时无需更改。
  • 自动生成 /? 帮助页面: 包括考虑控制台宽度的漂亮格式。
  • 加载/保存命令行参数到文件中: 如果您有多个大型、复杂的命令行参数集需要多次运行,则这特别有用。

  • 2
    我发现BizArk的命令行解析器比其他解析器更容易且流畅。强烈推荐! - Boris Modylevsky

    13

    9

    2
    使用起来非常简单,他们的网站很棒。然而,他们的语法并不是很直观:myapp myverb -argname argvalue(必须有-argname)或者 myapp -help(通常是--help)。 - Wernight
    @Wernight,您可以在动词上使用IsDefault参数,以便它可以被省略。我没有找到对位置参数的支持,但是当我自己解析命令行时,我只使用了位置参数。在我看来,使用后跟值的命名参数更清晰明了。 - Loudenvier

    5

    有很多解决这个问题的方案。为了全面并提供选择,如果有人需要,我在我的谷歌代码库中添加了两个有用的类的答案。

    第一个是ArgumentList,它仅负责解析命令行参数。它收集由开关“/x:y”或“-x=y”定义的名称-值对,并收集“未命名”的条目列表。它的基本用法在此处讨论查看此类

    这个问题的第二部分是CommandInterpreter,它可以将你的.Net类创建为完全功能的命令行应用程序。例如:

    using CSharpTest.Net.Commands;
    static class Program
    {
        static void Main(string[] args)
        {
            new CommandInterpreter(new Commands()).Run(args);
        }
        //example ‘Commands’ class:
        class Commands
        {
            public int SomeValue { get; set; }
            public void DoSomething(string svalue, int ivalue)
            { ... }
    

    通过以上示例代码,您可以运行以下内容:

    Program.exe DoSomething "字符串值" 5

    -- 或者 --

    Program.exe dosomething /ivalue=5 -svalue:"字符串值"

    这就是如此简单或者说复杂,取决于您的需要。您可以查看源代码查看帮助下载二进制文件


    4

    我喜欢那个,因为你可以“定义规则”来确定参数的必要性或非必要性...

    如果你是Unix用户,那么你可能会喜欢GNU Getopt .NET移植版。


    4

    您可能会喜欢我的一个工具 Rug.Cmd

    这是一个易于使用且可扩展的命令行参数解析器。它可以处理:布尔值、加号/减号、字符串、字符串列表、CSV、枚举等类型。

    内置'/?'帮助模式。

    内置'/??'和'/?D'文档生成模式。

    static void Main(string[] args) 
    {            
        // create the argument parser
        ArgumentParser parser = new ArgumentParser("ArgumentExample", "Example of argument parsing");
    
        // create the argument for a string
        StringArgument StringArg = new StringArgument("String", "Example string argument", "This argument demonstrates string arguments");
    
        // add the argument to the parser 
        parser.Add("/", "String", StringArg);
    
        // parse arguemnts
        parser.Parse(args);
    
        // did the parser detect a /? argument 
        if (parser.HelpMode == false) 
        {
            // was the string argument defined 
            if (StringArg.Defined == true)
            {
                // write its value
                RC.WriteLine("String argument was defined");
                RC.WriteLine(StringArg.Value);
            }
        }
    }
    

    编辑:这是我的项目,因此本答案不应被视为第三方的认可。话虽如此,我确实在编写每个基于命令行的程序时都使用它,它是开源的,我希望其他人也能从中受益。


    提醒一下,你应该加上一个小免责声明,表明自己与Rug.Cmd项目有关联(如FAQ中所述):http://stackoverflow.com/faq#promotion -- 这并不是什么大问题,因为你在推广一个开源项目,但添加一个免责声明仍然是很好的做法;另外,顺便点个赞...看起来做得相当不错。 - Jason Down
    感谢您指出这一点并给予支持,我会确保更明确地表明我的从属关系。 - Phill Tew
    没关系...有些人对这种事情很挑剔(我不是其中之一),所以我喜欢提前告诉大家。再次强调,对于开源项目来说通常不是问题。主要是为了防止人们为他们的(付费)产品进行推荐而滥发垃圾信息。 - Jason Down

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