【问题标题】:Split string containing command-line parameters into string[] in C#在 C# 中将包含命令行参数的字符串拆分为 string[]
【发布时间】:2014-09-18 13:14:22
【问题描述】:

我有一个字符串,其中包含要传递给另一个可执行文件的命令行参数,我需要提取包含各个参数的字符串 [],就像在命令上指定命令时 C# 一样-线。当通过反射执行另一个程序集入口点时,将使用 string[]。

这有标准功能吗?或者是否有正确拆分参数的首选方法(正则表达式?)?它必须正确处理可能包含空格的 '"' 分隔字符串,所以我不能只在 ' ' 上拆分。

示例字符串:

string parameterString = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo";

示例结果:

string[] parameterArray = new string[] { 
  @"/src:C:\tmp\Some Folder\Sub Folder",
  @"/users:abcdefg@hijkl.com",
  @"tasks:SomeTask,Some Other Task",
  @"-someParam",
  @"foo"
};

我不需要命令行解析库,只需要一种获取应该生成的String[]的方法。

更新:我必须更改预期结果以匹配 C# 实际生成的结果(删除了拆分字符串中多余的“)

【问题讨论】:

  • 每次有人回复时,您似乎都会根据帖子中没有的材料提出异议。我建议您使用此材料更新您的帖子。您可能会得到更好的答案。
  • 好问题,正在寻找相同的问题。希望找到有人说“嘿 .net 在这里暴露了……” :) 如果我在某个时候遇到它,我会在这里发布它,即使这已经是 6 岁了。仍然是一个有效的问题!
  • 我在下面的答案中创建了一个纯托管版本,因为我也需要这个功能。

标签: c# command-line text-parsing


【解决方案1】:

因为我想要与 OP 相同的行为(拆分字符串与 windows cmd 完全相同)我写了一堆测试用例并测试了这里发布的答案:

    Test( 0, m, "One",                    new[] { "One" });
    Test( 1, m, "One ",                   new[] { "One" });
    Test( 2, m, " One",                   new[] { "One" });
    Test( 3, m, " One ",                  new[] { "One" });
    Test( 4, m, "One Two",                new[] { "One", "Two" });
    Test( 5, m, "One  Two",               new[] { "One", "Two" });
    Test( 6, m, "One   Two",              new[] { "One", "Two" });
    Test( 7, m, "\"One Two\"",            new[] { "One Two" });
    Test( 8, m, "One \"Two Three\"",      new[] { "One", "Two Three" });
    Test( 9, m, "One \"Two Three\" Four", new[] { "One", "Two Three", "Four" });
    Test(10, m, "One=\"Two Three\" Four", new[] { "One=Two Three", "Four" });
    Test(11, m, "One\"Two Three\" Four",  new[] { "OneTwo Three", "Four" });
    Test(12, m, "One\"Two Three   Four",  new[] { "OneTwo Three   Four" });
    Test(13, m, "\"One Two\"",            new[] { "One Two" });
    Test(14, m, "One\" \"Two",            new[] { "One Two" });
    Test(15, m, "\"One\"  \"Two\"",       new[] { "One", "Two" });
    Test(16, m, "One\\\"  Two",           new[] { "One\"", "Two" });
    Test(17, m, "\\\"One\\\"  Two",       new[] { "\"One\"", "Two" });
    Test(18, m, "One\"",                  new[] { "One" });
    Test(19, m, "\"One",                  new[] { "One" });
    Test(20, m, "One \"\"",               new[] { "One", "" });
    Test(21, m, "One \"",                 new[] { "One", "" });
    Test(22, m, "1 A=\"B C\"=D 2",        new[] { "1", "A=B C=D", "2" });
    Test(23, m, "1 A=\"B \\\" C\"=D 2",   new[] { "1", "A=B \" C=D", "2" });
    Test(24, m, "1 \\A 2",                new[] { "1", "\\A", "2" });
    Test(25, m, "1 \\\" 2",               new[] { "1", "\"", "2" });
    Test(26, m, "1 \\\\\" 2",             new[] { "1", "\\\"", "2" });
    Test(27, m, "\"",                     new[] { "" });
    Test(28, m, "\\\"",                   new[] { "\"" });
    Test(29, m, "'A B'",                  new[] { "'A", "B'" });
    Test(30, m, "^",                      new[] { "^" });
    Test(31, m, "^A",                     new[] { "A" });
    Test(32, m, "^^",                     new[] { "^" });
    Test(33, m, "\\^^",                   new[] { "\\^" });
    Test(34, m, "^\\\\",                  new[] { "\\\\" });
    Test(35, m, "^\"A B\"",               new[] { "A B" });

    // Test cases Anton

    Test(36, m, @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo", new[] { @"/src:C:\tmp\Some Folder\Sub Folder", @"/users:abcdefg@hijkl.com", @"tasks:SomeTask,Some Other Task", @"-someParam", @"foo" });

    // Test cases Daniel Earwicker 

    Test(37, m, "",            new string[] { });
    Test(38, m, "a",           new[] { "a" });
    Test(39, m, " abc ",       new[] { "abc" });
    Test(40, m, "a b ",        new[] { "a", "b" });
    Test(41, m, "a b \"c d\"", new[] { "a", "b", "c d" });

    // Test cases Fabio Iotti 

    Test(42, m, "this is a test ",    new[] { "this", "is", "a", "test" });
    Test(43, m, "this \"is a\" test", new[] { "this", "is a", "test" });

    // Test cases Kevin Thach

    Test(44, m, "\"C:\\Program Files\"",                       new[] { "C:\\Program Files" });
    Test(45, m, "\"He whispered to her \\\"I love you\\\".\"", new[] { "He whispered to her \"I love you\"." });

“预期”值来自在我的机器(Win10 x64)上使用 cmd.exe 和一个简单的打印程序直接测试它:

static void Main(string[] args) => Console.Out.WriteLine($"Count := {args.Length}\n{string.Join("\n", args.Select((v,i) => $"[{i}] => '{v}'"))}");

这些是结果:


Solution                      | Failed Tests
------------------------------|------------------------------------- 
Atif Aziz (749653)            | 2, 3, 10, 11, 12, 14, 16, 17, 18, 26, 28, 31, 32, 33, 34, 35, 36, 37, 39, 45
Jeffrey L Whitledge (298968)  | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Daniel Earwicker (298990)     | 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 45
Anton (299795)                | 12, 16, 17, 18, 19, 21, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
CS. (467313)                  | 12, 18, 19, 21, 27, 31, 32, 33, 34, 35
Vapour in the Alley (2132004) | 10, 11, 12, 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 45
Monoman (7774211)             | 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
Thomas Petersson (19091999)   | 2, 3, 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 39, 45
Fabio Iotti (19725880)        | 1, 2, 3, 7, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 22, 23, 25, 26, 28, 29, 30, 35, 36, 37, 39, 40, 42, 44, 45
ygoe (23961658)               | 26, 31, 32, 33, 34, 35
Kevin Thach (24829691)        | 10, 11, 12, 14, 18, 19, 20, 21, 22, 23, 26, 27, 31, 32, 33, 34, 35, 36
Lucas De Jesus (31621370)     | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45
HarryP (48008872)             | 24, 26, 31, 32, 33, 34, 35
TylerY86 (53290784)           | 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 41, 43, 44, 45
Louis Somers (55903304)       | 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 39, 41, 43, 44, 45
user2126375 (58233585)        | 5, 6, 15, 16, 17, 31, 32, 33, 34, 35
DilipNannaware (59131568)     | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Mikescher (this)              | -

因为没有答案似乎是正确的(至少基于我的用例)这是我的解决方案,它目前通过了所有测试用例(但如果有人有其他(失败)极端情况,请发表评论):

public static IEnumerable<string> SplitArgs(string commandLine)
{
    var result = new StringBuilder();

    var quoted = false;
    var escaped = false;
    var started = false;
    var allowcaret = false;
    for (int i = 0; i < commandLine.Length; i++)
    {
        var chr = commandLine[i];

        if (chr == '^' && !quoted)
        {
            if (allowcaret)
            {
                result.Append(chr);
                started = true;
                escaped = false;
                allowcaret = false;
            }
            else if (i + 1 < commandLine.Length && commandLine[i + 1] == '^')
            {
                allowcaret = true;
            }
            else if (i + 1 == commandLine.Length)
            {
                result.Append(chr);
                started = true;
                escaped = false;
            }
        }
        else if (escaped)
        {
            result.Append(chr);
            started = true;
            escaped = false;
        }
        else if (chr == '"')
        {
            quoted = !quoted;
            started = true;
        }
        else if (chr == '\\' && i + 1 < commandLine.Length && commandLine[i + 1] == '"')
        {
            escaped = true;
        }
        else if (chr == ' ' && !quoted)
        {
            if (started) yield return result.ToString();
            result.Clear();
            started = false;
        }
        else
        {
            result.Append(chr);
            started = true;
        }
    }

    if (started) yield return result.ToString();
}

我用来生成测试结果的代码可以找到here

【讨论】:

  • 最佳答案在这里。你会考虑制作一个nuget包吗? ?
  • 我发布了 github repo 个人贡献。这将允许公众对单元测试和实现做出贡献。 @Mikescher 你想被设置为 repo 贡献者吗?
【解决方案2】:

我编写了一个方法来将文件名与其参数分开,用于ProcessStartInfo,它需要将文件名和参数字符串分开。

例如"C:\Users\Me\Something.exe" -a 结果会给出{ "C:\Users\Me\Something.exe", "-a" }

代码如下:

    public static string[] SplitCommandFromArgs(string commandLine)
    {
        commandLine = commandLine.Trim();
        if (commandLine[0] == '"')
        {
            bool isEscaped = false;
            for (int c = 1; c < commandLine.Length; c++)
            {
                if (commandLine[c] == '"' && !isEscaped)
                {
                    return new string[] { commandLine.Substring(1, c - 1), commandLine.Substring(c + 1).Trim() };
                }
                isEscaped = commandLine[c] == '\\';
            }
        }
        else
        {
            for (int c = 1; c < commandLine.Length; c++) {
                if (commandLine[c] == ' ')
                {
                    return new string[] { commandLine.Substring(0, c), commandLine.Substring(c).Trim() };
                }
            }
        }
        return new string[] { commandLine, "" };
    }

【讨论】:

    【解决方案3】:

    有一个 NuGet 包,其中包含您需要的功能:

    Microsoft.CodeAnalysis.Common 包含类 CommandLineParser 和方法 SplitCommandLineIntoArguments

    你可以这样使用它:

    using Microsoft.CodeAnalysis;
    // [...]
    var cli = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo";
    var cliArgs = CommandLineParser.SplitCommandLineIntoArguments(cli, true);
    
    Console.WriteLine(string.Join('\n', cliArgs));
    // prints out:
    // /src:"C:\tmp\Some Folder\Sub Folder"
    // /users:"abcdefg@hijkl.com"
    // tasks:"SomeTask,Some Other Task"
    // -someParam
    // foo
    

    【讨论】:

      【解决方案4】:

      这是将空格(单个或多个空格)视为命令行参数分隔符并返回真正的命令行参数的解决方案:

      static string[] ParseMultiSpacedArguments(string commandLine)
      {
          var isLastCharSpace = false;
          char[] parmChars = commandLine.ToCharArray();
          bool inQuote = false;
          for (int index = 0; index < parmChars.Length; index++)
          {
              if (parmChars[index] == '"')
                  inQuote = !inQuote;
              if (!inQuote && parmChars[index] == ' ' && !isLastCharSpace)
                  parmChars[index] = '\n';
      
              isLastCharSpace = parmChars[index] == '\n' || parmChars[index] == ' ';
          }
      
          return (new string(parmChars)).Split('\n');
      }
      

      【讨论】:

        【解决方案5】:

        我已经实现了状态机,使其具有与将 args 传递到 .NET 应用程序并在 static void Main(string[] args) 方法中处理一样的解析器结果。

            public static IList<string> ParseCommandLineArgsString(string commandLineArgsString)
            {
                List<string> args = new List<string>();
        
                commandLineArgsString = commandLineArgsString.Trim();
                if (commandLineArgsString.Length == 0)
                    return args;
        
                int index = 0;
                while (index != commandLineArgsString.Length)
                {
                    args.Add(ReadOneArgFromCommandLineArgsString(commandLineArgsString, ref index));
                }
        
                return args;
            }
        
            private static string ReadOneArgFromCommandLineArgsString(string line, ref int index)
            {
                if (index >= line.Length)
                    return string.Empty;
        
                var sb = new StringBuilder(512);
                int state = 0;
                while (true)
                {
                    char c = line[index];
                    index++;
                    switch (state)
                    {
                        case 0: //string outside quotation marks
                            if (c == '\\') //possible escaping character for quotation mark otherwise normal character
                            {
                                state = 1;
                            }
                            else if (c == '"') //opening quotation mark for string between quotation marks
                            {
                                state = 2;
                            }
                            else if (c == ' ') //closing arg
                            {
                                return sb.ToString();
                            }
                            else
                            {
                                sb.Append(c);
                            }
        
                            break;
                        case 1: //possible escaping \ for quotation mark or normal character
                            if (c == '"') //If escaping quotation mark only quotation mark is added into result
                            {
                                state = 0;
                                sb.Append(c);
                            }
                            else // \ works as not-special character
                            {
                                state = 0;
                                sb.Append('\\');
                                index--;
                            }
        
                            break;
                        case 2: //string between quotation marks
                            if (c == '"') //quotation mark in string between quotation marks can be escape mark for following quotation mark or can be ending quotation mark for string between quotation marks
                            {
                                state = 3;
                            }
                            else if (c == '\\') //escaping \ for possible following quotation mark otherwise normal character
                            {
                                state = 4;
                            }
                            else //text in quotation marks
                            {
                                sb.Append(c);
                            }
        
                            break;
                        case 3: //quotation mark in string between quotation marks
                            if (c == '"') //Quotation mark after quotation mark - that means that this one is escaped and can added into result and we will stay in string between quotation marks state
                            {
                                state = 2;
                                sb.Append(c);
                            }
                            else //we had two consecutive quotation marks - this means empty string but the following chars (until space) will be part of same arg result as well
                            {
                                state = 0;
                                index--;
                            }
        
                            break;
                        case 4: //possible escaping \ for quotation mark or normal character in string between quotation marks
                            if (c == '"') //If escaping quotation mark only quotation mark added into result
                            {
                                state = 2;
                                sb.Append(c);
                            }
                            else
                            {
                                state = 2;
                                sb.Append('\\');
                                index--;
                            }
        
                            break;
                    }
        
                    if (index == line.Length)
                        return sb.ToString();
                }
            }
        

        【讨论】:

          【解决方案6】:

          在这里找不到我喜欢的东西。我讨厌为一个小的命令行使用 yield 魔法来搞乱堆栈(如果它是一个 TB 的流,那就是另一回事了)。

          这是我的看法,它支持带双引号的引号转义,如下所示:

          param="a 15"" 屏幕还不错" param2='a 15" 屏幕还不错' param3="" param4= /param5

          结果:

          param="15" 屏幕还不错"

          param2='15" 屏幕还不错'

          param3=""

          param4=

          /param5

          public static string[] SplitArguments(string commandLine)
          {
              List<string> args         = new List<string>();
              List<char>   currentArg   = new List<char>();
              char?        quoteSection = null; // Keeps track of a quoted section (and the type of quote that was used to open it)
              char[]       quoteChars   = new[] {'\'', '\"'};
              char         previous     = ' '; // Used for escaping double quotes
          
              for (var index = 0; index < commandLine.Length; index++)
              {
                  char c = commandLine[index];
                  if (quoteChars.Contains(c))
                  {
                      if (previous == c) // Escape sequence detected
                      {
                          previous = ' '; // Prevent re-escaping
                          if (!quoteSection.HasValue)
                          {
                              quoteSection = c; // oops, we ended the quoted section prematurely
                              continue;         // don't add the 2nd quote (un-escape)
                          }
          
                          if (quoteSection.Value == c)
                              quoteSection = null; // appears to be an empty string (not an escape sequence)
                      }
                      else if (quoteSection.HasValue)
                      {
                          if (quoteSection == c)
                              quoteSection = null; // End quoted section
                      }
                      else
                          quoteSection = c; // Start quoted section
                  }
                  else if (char.IsWhiteSpace(c))
                  {
                      if (!quoteSection.HasValue)
                      {
                          args.Add(new string(currentArg.ToArray()));
                          currentArg.Clear();
                          previous = c;
                          continue;
                      }
                  }
          
                  currentArg.Add(c);
                  previous = c;
              }
          
              if (currentArg.Count > 0)
                  args.Add(new string(currentArg.ToArray()));
          
              return args.ToArray();
          }
          

          【讨论】:

            【解决方案7】:

            哎呀。这一切……哎呀。 But this is legit official. 来自 Microsoft 的 C# for .NET Core,可能仅适用于 Windows,也可能是跨平台的,但已获得 MIT 许可。

            选择花絮、方法声明和著名的 cmets;

            internal static unsafe string[] InternalCreateCommandLine(bool includeArg0)
            private static unsafe int SegmentCommandLine(char * pCmdLine, string[] argArray, bool includeArg0)
            private static unsafe int ScanArgument0(ref char* psrc, char[] arg)
            private static unsafe int ScanArgument(ref char* psrc, ref bool inquote, char[] arg)
            

            -

            // First, parse the program name (argv[0]). Argv[0] is parsed under special rules. Anything up to 
            // the first whitespace outside a quoted subtring is accepted. Backslashes are treated as normal 
            // characters.
            

            -

            // Rules: 2N backslashes + " ==> N backslashes and begin/end quote
            //      2N+1 backslashes + " ==> N backslashes + literal "
            //         N backslashes     ==> N backslashes
            

            这是从 .NET Framework 移植到 .NET Core 的代码,我假设是 MSVC C 库或 CommandLineToArgvW

            这是我用正则表达式处理一些恶作剧并忽略零位参数的半心半意尝试。这有点神奇。

            private static readonly Regex RxWinArgs
              = new Regex("([^\\s\"]+\"|((?<=\\s|^)(?!\"\"(?!\"))\")+)(\"\"|.*?)*\"[^\\s\"]*|[^\\s]+",
                RegexOptions.Compiled
                | RegexOptions.Singleline
                | RegexOptions.ExplicitCapture
                | RegexOptions.CultureInvariant);
            
            internal static IEnumerable<string> ParseArgumentsWindows(string args) {
              var match = RxWinArgs.Match(args);
            
              while (match.Success) {
                yield return match.Value;
                match = match.NextMatch();
              }
            }
            

            在古怪的生成输出上进行了相当多的测试。它的输出与猴子输入并通过CommandLineToArgvW 运行的内容相当比例。

            【解决方案8】:

            我喜欢迭代器,现在LINQ 使IEnumerable&lt;String&gt; 像字符串数组一样易于使用,所以我遵循Jeffrey L Whitledge's answer 的精神是(作为string 的扩展方法):

            public static IEnumerable<string> ParseArguments(this string commandLine)
            {
                if (string.IsNullOrWhiteSpace(commandLine))
                    yield break;
            
                var sb = new StringBuilder();
                bool inQuote = false;
                foreach (char c in commandLine) {
                    if (c == '"' && !inQuote) {
                        inQuote = true;
                        continue;
                    }
            
                    if (c != '"' && !(char.IsWhiteSpace(c) && !inQuote)) {
                        sb.Append(c);
                        continue;
                    }
            
                    if (sb.Length > 0) {
                        var result = sb.ToString();
                        sb.Clear();
                        inQuote = false;
                        yield return result;
                    }
                }
            
                if (sb.Length > 0)
                    yield return sb.ToString();
            }
            

            【讨论】:

              【解决方案9】:

              用途:

              public static string[] SplitArguments(string args) {
                  char[] parmChars = args.ToCharArray();
                  bool inSingleQuote = false;
                  bool inDoubleQuote = false;
                  bool escaped = false;
                  bool lastSplitted = false;
                  bool justSplitted = false;
                  bool lastQuoted = false;
                  bool justQuoted = false;
              
                  int i, j;
              
                  for(i=0, j=0; i<parmChars.Length; i++, j++) {
                      parmChars[j] = parmChars[i];
              
                      if(!escaped) {
                          if(parmChars[i] == '^') {
                              escaped = true;
                              j--;
                          } else if(parmChars[i] == '"' && !inSingleQuote) {
                              inDoubleQuote = !inDoubleQuote;
                              parmChars[j] = '\n';
                              justSplitted = true;
                              justQuoted = true;
                          } else if(parmChars[i] == '\'' && !inDoubleQuote) {
                              inSingleQuote = !inSingleQuote;
                              parmChars[j] = '\n';
                              justSplitted = true;
                              justQuoted = true;
                          } else if(!inSingleQuote && !inDoubleQuote && parmChars[i] == ' ') {
                              parmChars[j] = '\n';
                              justSplitted = true;
                          }
              
                          if(justSplitted && lastSplitted && (!lastQuoted || !justQuoted))
                              j--;
              
                          lastSplitted = justSplitted;
                          justSplitted = false;
              
                          lastQuoted = justQuoted;
                          justQuoted = false;
                      } else {
                          escaped = false;
                      }
                  }
              
                  if(lastQuoted)
                      j--;
              
                  return (new string(parmChars, 0, j)).Split(new[] { '\n' });
              }
              

              根据Vapour in the Alley的回答,这个也支持^转义。

              例子:

              • 这是一个测试
                • 这个
                • 一个
                • 测试
              • 这个“是”测试
                • 这个
                • 是一个
                • 测试
              • 这个 ^"is a^" 测试
                • 这个
                • “是
                • 一个"
                • 测试
              • 这个“”“是一个^^测试”
                • 这个
                • 是一个^测试

              它还支持多个空格(每个空格块只中断一次参数)。

              【讨论】:

              • 这三个中的最后一个以某种方式干扰 Markdown 并且没有按预期呈现。
              • 固定为零宽度空间。
              【解决方案10】:

              这是完成工作的单行代码(请参阅在 BurstCmdLineArgs(...) 方法中完成所有工作的单行代码)。

              不是我所说的最易读的代码行,但为了便于阅读,您可以将其拆分。目的很简单,并且不适用于所有参数情况(例如文件名参数中包含拆分字符串字符分隔符)。

              此解决方案在我使用它的解决方案中运行良好。就像我说的那样,它可以在没有大量代码的情况下完成工作,以处理所有可能的参数格式 n-factorial。

              using System;
              using System.Collections.Generic;
              using System.Linq;
              
              namespace CmdArgProcessor
              {
                  class Program
                  {
                      static void Main(string[] args)
                      {
                          // test switches and switches with values
                          // -test1 1 -test2 2 -test3 -test4 -test5 5
              
                          string dummyString = string.Empty;
              
                          var argDict = BurstCmdLineArgs(args);
              
                          Console.WriteLine("Value for switch = -test1: {0}", argDict["test1"]);
                          Console.WriteLine("Value for switch = -test2: {0}", argDict["test2"]);
                          Console.WriteLine("Switch -test3 is present? {0}", argDict.TryGetValue("test3", out dummyString));
                          Console.WriteLine("Switch -test4 is present? {0}", argDict.TryGetValue("test4", out dummyString));
                          Console.WriteLine("Value for switch = -test5: {0}", argDict["test5"]);
              
                          // Console output:
                          //
                          // Value for switch = -test1: 1
                          // Value for switch = -test2: 2
                          // Switch -test3 is present? True
                          // Switch -test4 is present? True
                          // Value for switch = -test5: 5
                      }
              
                      public static Dictionary<string, string> BurstCmdLineArgs(string[] args)
                      {
                          var argDict = new Dictionary<string, string>();
              
                          // Flatten the args in to a single string separated by a space.
                          // Then split the args on the dash delimiter of a cmd line "switch".
                          // E.g. -mySwitch myValue
                          //  or -JustMySwitch (no value)
                          //  where: all values must follow a switch.
                          // Then loop through each string returned by the split operation.
                          // If the string can be split again by a space character,
                          // then the second string is a value to be paired with a switch,
                          // otherwise, only the switch is added as a key with an empty string as the value.
                          // Use dictionary indexer to retrieve values for cmd line switches.
                          // Use Dictionary::ContainsKey(...) where only a switch is recorded as the key.
                          string.Join(" ", args).Split('-').ToList().ForEach(s => argDict.Add(s.Split()[0], (s.Split().Count() > 1 ? s.Split()[1] : "")));
              
                          return argDict;
                      }
                  }
              }
              

              【讨论】:

                【解决方案11】:

                试试这个代码:

                    string[] str_para_linha_comando(string str, out int argumentos)
                    {
                        string[] linhaComando = new string[32];
                        bool entre_aspas = false;
                        int posicao_ponteiro = 0;
                        int argc = 0;
                        int inicio = 0;
                        int fim = 0;
                        string sub;
                
                        for(int i = 0; i < str.Length;)
                        {
                            if (entre_aspas)
                            {
                                // Está entre aspas
                                sub = str.Substring(inicio+1, fim - (inicio+1));
                                linhaComando[argc - 1] = sub;
                
                                posicao_ponteiro += ((fim - posicao_ponteiro)+1);
                                entre_aspas = false;
                                i = posicao_ponteiro;
                            }
                            else
                            {
                            tratar_aspas:
                                if (str.ElementAt(i) == '\"')
                                {
                                    inicio = i;
                                    fim = str.IndexOf('\"', inicio + 1);
                                    entre_aspas = true;
                                    argc++;
                                }
                                else
                                {
                                    // Se não for aspas, então ler até achar o primeiro espaço em branco
                                    if (str.ElementAt(i) == ' ')
                                    {
                                        if (str.ElementAt(i + 1) == '\"')
                                        {
                                            i++;
                                            goto tratar_aspas;
                                        }
                
                                        // Pular os espaços em branco adiconais
                                        while(str.ElementAt(i) == ' ') i++;
                
                                        argc++;
                                        inicio = i;
                                        fim = str.IndexOf(' ', inicio);
                                        if (fim == -1) fim = str.Length;
                                        sub = str.Substring(inicio, fim - inicio);
                                        linhaComando[argc - 1] = sub;
                                        posicao_ponteiro += (fim - posicao_ponteiro);
                
                                        i = posicao_ponteiro;
                                        if (posicao_ponteiro == str.Length) break;
                                    }
                                    else
                                    {
                                        argc++;
                                        inicio = i;
                                        fim = str.IndexOf(' ', inicio);
                                        if (fim == -1) fim = str.Length;
                
                                        sub = str.Substring(inicio, fim - inicio);
                                        linhaComando[argc - 1] = sub;
                                        posicao_ponteiro += fim - posicao_ponteiro;
                                        i = posicao_ponteiro;
                                        if (posicao_ponteiro == str.Length) break;
                                    }
                                }
                            }
                        }
                
                        argumentos = argc;
                
                        return linhaComando;
                    }
                

                它是用葡萄牙语写的。

                【讨论】:

                • 更多的文档是葡萄牙语
                • @EnamulHassan 我会说代码也是葡萄牙语,例如posicao_ponteiro += ((fim - posicao_ponteiro)+1);.
                【解决方案12】:

                完全托管的解决方案可能会有所帮助。 WINAPI 函数的“问题”cmets 太多,在其他平台上不可用。这是我的代码,它具有明确定义的行为(您可以根据需要进行更改)。

                它应该与 .NET/Windows 在提供 string[] args 参数时所做的相同,并且我已经将它与一些“有趣”的值进行了比较。

                这是一个经典的状态机实现,它从输入字符串中获取每个单个字符并将其解释为当前状态,产生输出和新状态。状态定义在变量escapeinQuotehadQuoteprevCh中,输出收集在currentArgargs中。

                我在真实命令提示符 (Windows 7) 上通过实验发现的一些特性:\\ 产生 \\" 产生 """ 在引用范围内产生 @987654333 @。

                ^ 字符似乎也很神奇:当它不加倍时它总是消失。否则它对真正的命令行没有影响。我的实现不支持这一点,因为我还没有找到这种行为的模式。也许有人知道更多。

                不适合这种模式的是以下命令:

                cmd /c "argdump.exe "a b c""
                

                cmd 命令似乎捕获了外部引号并逐字记录其余部分。这里面一定有什么特殊的魔法酱。

                我没有对我的方法进行任何基准测试,但认为它相当快。它不使用Regex,也不进行任何字符串连接,而是使用StringBuilder 来收集参数的字符并将它们放入列表中。

                /// <summary>
                /// Reads command line arguments from a single string.
                /// </summary>
                /// <param name="argsString">The string that contains the entire command line.</param>
                /// <returns>An array of the parsed arguments.</returns>
                public string[] ReadArgs(string argsString)
                {
                    // Collects the split argument strings
                    List<string> args = new List<string>();
                    // Builds the current argument
                    var currentArg = new StringBuilder();
                    // Indicates whether the last character was a backslash escape character
                    bool escape = false;
                    // Indicates whether we're in a quoted range
                    bool inQuote = false;
                    // Indicates whether there were quotes in the current arguments
                    bool hadQuote = false;
                    // Remembers the previous character
                    char prevCh = '\0';
                    // Iterate all characters from the input string
                    for (int i = 0; i < argsString.Length; i++)
                    {
                        char ch = argsString[i];
                        if (ch == '\\' && !escape)
                        {
                            // Beginning of a backslash-escape sequence
                            escape = true;
                        }
                        else if (ch == '\\' && escape)
                        {
                            // Double backslash, keep one
                            currentArg.Append(ch);
                            escape = false;
                        }
                        else if (ch == '"' && !escape)
                        {
                            // Toggle quoted range
                            inQuote = !inQuote;
                            hadQuote = true;
                            if (inQuote && prevCh == '"')
                            {
                                // Doubled quote within a quoted range is like escaping
                                currentArg.Append(ch);
                            }
                        }
                        else if (ch == '"' && escape)
                        {
                            // Backslash-escaped quote, keep it
                            currentArg.Append(ch);
                            escape = false;
                        }
                        else if (char.IsWhiteSpace(ch) && !inQuote)
                        {
                            if (escape)
                            {
                                // Add pending escape char
                                currentArg.Append('\\');
                                escape = false;
                            }
                            // Accept empty arguments only if they are quoted
                            if (currentArg.Length > 0 || hadQuote)
                            {
                                args.Add(currentArg.ToString());
                            }
                            // Reset for next argument
                            currentArg.Clear();
                            hadQuote = false;
                        }
                        else
                        {
                            if (escape)
                            {
                                // Add pending escape char
                                currentArg.Append('\\');
                                escape = false;
                            }
                            // Copy character from input, no special meaning
                            currentArg.Append(ch);
                        }
                        prevCh = ch;
                    }
                    // Save last argument
                    if (currentArg.Length > 0 || hadQuote)
                    {
                        args.Add(currentArg.ToString());
                    }
                    return args.ToArray();
                }
                

                【讨论】:

                  【解决方案13】:

                  你可以看看我昨天发布的代码:

                  [C#] Path & arguments strings

                  它将文件名 + 参数拆分为字符串 []。处理短路径、环境变量和丢失的文件扩展名。

                  (最初是用于注册表中的 UninstallString。)

                  【讨论】:

                    【解决方案14】:

                    我使用了the answer from Jeffrey L Whitledge 并对其进行了一些改进。

                    它现在支持单引号和双引号。您可以通过使用其他类型的引号在参数本身中使用引号。

                    它还会从参数中去除引号,因为这些引号对参数信息没有贡献。

                        public static string[] SplitArguments(string commandLine)
                        {
                            var parmChars = commandLine.ToCharArray();
                            var inSingleQuote = false;
                            var inDoubleQuote = false;
                            for (var index = 0; index < parmChars.Length; index++)
                            {
                                if (parmChars[index] == '"' && !inSingleQuote)
                                {
                                    inDoubleQuote = !inDoubleQuote;
                                    parmChars[index] = '\n';
                                }
                                if (parmChars[index] == '\'' && !inDoubleQuote)
                                {
                                    inSingleQuote = !inSingleQuote;
                                    parmChars[index] = '\n';
                                }
                                if (!inSingleQuote && !inDoubleQuote && parmChars[index] == ' ')
                                    parmChars[index] = '\n';
                            }
                            return (new string(parmChars)).Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
                        }
                    

                    【讨论】:

                      【解决方案15】:

                      这个The Code Project article 是我过去使用的。这是一段很好的代码,但它可能会起作用。

                      MSDN article 是我能找到的唯一一个解释 C# 如何解析命令行参数的东西。

                      【讨论】:

                      • 我尝试在 C# 库中进行反射,但它转到了我没有代码的本机 C++ 调用,并且在没有 p 调用的情况下看不到任何调用方式.我也不想要一个命令行解析库,我只想要字符串[]。
                      • 反映 .NET 也无济于事。查看Monosourcecodesuggested,这个参数拆分不是由CLR完成的,而是已经来自操作系统。想想C主函数的argc、argv参数。所以除了 OS API 没有什么可以重用的。
                      【解决方案16】:

                      我不确定我是否理解你,但是用作分隔符的字符是否也可以在文本中找到? (除了它是用双“?”转义的)

                      如果是这样,我会创建一个for 循环,并用 (或另一个“安全”字符,但确保它只替换 ,而不是

                      在迭代字符串之后,我会像之前发布的那样,拆分字符串,但现在在字符 上。

                      【讨论】:

                      • 双“”是因为它是一个@“..”字符串文字,@“..”字符串中的双“”相当于普通字符串中的\转义“
                      • "唯一的限制(我相信)是字符串是用空格分隔的,除非空格出现在 "..." 块内" -> 可能是用火箭筒射击鸟,但是放一个布尔值,当引号内变为“真”时,如果在“真”内检测到空格,则继续,否则 =
                      【解决方案17】:

                      是的,字符串对象有一个名为Split() 的内置函数,它接受一个参数,指定要查找的字符作为分隔符,并返回一个字符串数组(string[]),其中包含各个值。

                      【讨论】:

                      • 这会错误地拆分 src:"C:\tmp\Some Folder\Sub Folder" 部分。
                      • 字符串中的引号会暂时关闭空格分割吗?
                      【解决方案18】:

                      除了Earwickergood and pure managed solution 之外,为了完整起见,值得一提的是,Windows 还提供了CommandLineToArgvW 函数,用于将字符串分解为字符串数组:

                      LPWSTR *CommandLineToArgvW(
                          LPCWSTR lpCmdLine, int *pNumArgs);
                      

                      解析 Unicode 命令行字符串 并返回一个指针数组 命令行参数,以及 在某种程度上,这些论点的计数 类似于标准C 运行时 argv 和 argc 值。

                      可以在“Converting Command Line String to Args[] using CommandLineToArgvW() API”中找到从 C# 调用此 API 并在托管代码中解压缩生成的字符串数组的示例。下面是相同代码的稍微简单的版本:

                      [DllImport("shell32.dll", SetLastError = true)]
                      static extern IntPtr CommandLineToArgvW(
                          [MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);
                      
                      public static string[] CommandLineToArgs(string commandLine)
                      {
                          int argc;
                          var argv = CommandLineToArgvW(commandLine, out argc);        
                          if (argv == IntPtr.Zero)
                              throw new System.ComponentModel.Win32Exception();
                          try
                          {
                              var args = new string[argc];
                              for (var i = 0; i < args.Length; i++)
                              {
                                  var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
                                  args[i] = Marshal.PtrToStringUni(p);
                              }
                      
                              return args;
                          }
                          finally
                          {
                              Marshal.FreeHGlobal(argv);
                          }
                      }
                      

                      【讨论】:

                      • 此函数要求您转义引号内路径的尾部反斜杠。 "C:\Program Files\" 必须是 "C:\Program Files\\" 才能正确解析字符串。
                      • 值得注意的是,CommandLineArgvW 期望第一个参数是程序名称,如果没有传入,则应用的解析魔法并不完全相同。你可以用类似的东西来伪造它:CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
                      • 为了完整起见,MSVCRT 不使用 CommandLineToArgvW() 将命令行转换为 argc/argv。它使用自己的代码,这是不同的。例如,尝试使用以下字符串调用 CreateProcess:a"b c"d e f。在 main() 中,您将获得 3 个参数(如 MSDN 中所述),但 CommandLineToArgvW()/GetCommandLineW() 组合将为您提供 2 个。
                      • 天哪,这真是一团糟。典型的 MS 汤。没有任何东西是规范化的,在 MS 世界中,KISS 从来没有受到尊重。
                      • 我发布了微软翻译的 MSVCRT 实现的跨平台版本和使用 Regex 的高精度近似。我知道这是旧的,但是嘿 - 没有正文卷轴。
                      【解决方案19】:

                      我认为 C# 应用程序没有单引号或 ^ 引号。 以下功能对我来说很好用:

                      public static IEnumerable<String> SplitArguments(string commandLine)
                      {
                          Char quoteChar = '"';
                          Char escapeChar = '\\';
                          Boolean insideQuote = false;
                          Boolean insideEscape = false;
                      
                          StringBuilder currentArg = new StringBuilder();
                      
                          // needed to keep "" as argument but drop whitespaces between arguments
                          Int32 currentArgCharCount = 0;                  
                      
                          for (Int32 i = 0; i < commandLine.Length; i++)
                          {
                              Char c = commandLine[i];
                              if (c == quoteChar)
                              {
                                  currentArgCharCount++;
                      
                                  if (insideEscape)
                                  {
                                      currentArg.Append(c);       // found \" -> add " to arg
                                      insideEscape = false;
                                  }
                                  else if (insideQuote)
                                  {
                                      insideQuote = false;        // quote ended
                                  }
                                  else
                                  {
                                      insideQuote = true;         // quote started
                                  }
                              }
                              else if (c == escapeChar)
                              {
                                  currentArgCharCount++;
                      
                                  if (insideEscape)   // found \\ -> add \\ (only \" will be ")
                                      currentArg.Append(escapeChar + escapeChar);       
                      
                                  insideEscape = !insideEscape;
                              }
                              else if (Char.IsWhiteSpace(c))
                              {
                                  if (insideQuote)
                                  {
                                      currentArgCharCount++;
                                      currentArg.Append(c);       // append whitespace inside quote
                                  }
                                  else
                                  {
                                      if (currentArgCharCount > 0)
                                          yield return currentArg.ToString();
                      
                                      currentArgCharCount = 0;
                                      currentArg.Clear();
                                  }
                              }
                              else
                              {
                                  currentArgCharCount++;
                                  if (insideEscape)
                                  {
                                      // found non-escaping backslash -> add \ (only \" will be ")
                                      currentArg.Append(escapeChar);                       
                                      currentArgCharCount = 0;
                                      insideEscape = false;
                                  }
                                  currentArg.Append(c);
                              }
                          }
                      
                          if (currentArgCharCount > 0)
                              yield return currentArg.ToString();
                      }
                      

                      【讨论】:

                        【解决方案20】:

                        Earwickergood and pure managed solution 无法处理这样的参数:

                        Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");
                        

                        它返回了 3 个元素:

                        "He whispered to her \"I
                        love
                        you\"."
                        

                        所以这里有一个支持“引用的\”转义\“引用”的修复:

                        public static IEnumerable<string> SplitCommandLine(string commandLine)
                        {
                            bool inQuotes = false;
                            bool isEscaping = false;
                        
                            return commandLine.Split(c => {
                                if (c == '\\' && !isEscaping) { isEscaping = true; return false; }
                        
                                if (c == '\"' && !isEscaping)
                                    inQuotes = !inQuotes;
                        
                                isEscaping = false;
                        
                                return !inQuotes && Char.IsWhiteSpace(c)/*c == ' '*/;
                                })
                                .Select(arg => arg.Trim().TrimMatchingQuotes('\"').Replace("\\\"", "\""))
                                .Where(arg => !string.IsNullOrEmpty(arg));
                        }
                        

                        用另外 2 个案例进行了测试:

                        Test("\"C:\\Program Files\"", "C:\\Program Files");
                        Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");
                        

                        还注意到使用CommandLineToArgvWAtif Azizaccepted answer 也失败了。它返回 4 个元素:

                        He whispered to her \ 
                        I 
                        love 
                        you". 
                        

                        希望这对将来寻找此类解决方案的人有所帮助。

                        【讨论】:

                        • 抱歉死灵,但这个解决方案仍然错过了像bla.exe aAAA"b\"ASDS\"c"dSADSD 这样的东西,导致aAAAb"ASDS"cdSADSD,这个解决方案将输出aAAA"b"ASDS"c"dSADSD。我可能会考虑将TrimMatchingQuotes 更改为Regex("(?&lt;!\\\\)\\\"") 并使用它like this
                        【解决方案21】:

                        在你的问题中你要求一个正则表达式,我是他们的忠实粉丝和用户,所以当我需要和你一样做同样的论点时,我在谷歌搜索后写了自己的正则表达式,但没有找到简单的解决方案.我喜欢简短的解决方案,所以我做了一个,这里是:

                                    var re = @"\G(""((""""|[^""])+)""|(\S+)) *";
                                    var ms = Regex.Matches(CmdLine, re);
                                    var list = ms.Cast<Match>()
                                                 .Select(m => Regex.Replace(
                                                     m.Groups[2].Success
                                                         ? m.Groups[2].Value
                                                         : m.Groups[4].Value, @"""""", @"""")).ToArray();
                        

                        它处理引号内的空格和引号,并将封闭的“”转换为“。随意使用代码!

                        【讨论】:

                          【解决方案22】:

                          这是对 Anton 代码的回复,该代码不适用于转义引号。我修改了 3 个地方。

                          1. SplitCommandLineArgumentsStringBuilder构造函数,将任何 \" 替换为 \r时间>
                          2. SplitCommandLineArgumentsfor-loop 中,我现在将 \r 字符替换回 \" .
                          3. SplitCommandLineArgument 方法从 private 更改为 public static

                          public static string[] SplitCommandLineArgument( String argumentString )
                          {
                              StringBuilder translatedArguments = new StringBuilder( argumentString ).Replace( "\\\"", "\r" );
                              bool InsideQuote = false;
                              for ( int i = 0; i < translatedArguments.Length; i++ )
                              {
                                  if ( translatedArguments[i] == '"' )
                                  {
                                      InsideQuote = !InsideQuote;
                                  }
                                  if ( translatedArguments[i] == ' ' && !InsideQuote )
                                  {
                                      translatedArguments[i] = '\n';
                                  }
                              }
                          
                              string[] toReturn = translatedArguments.ToString().Split( new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries );
                              for ( int i = 0; i < toReturn.Length; i++ )
                              {
                                  toReturn[i] = RemoveMatchingQuotes( toReturn[i] );
                                  toReturn[i] = toReturn[i].Replace( "\r", "\"" );
                              }
                              return toReturn;
                          }
                          
                          public static string RemoveMatchingQuotes( string stringToTrim )
                          {
                              int firstQuoteIndex = stringToTrim.IndexOf( '"' );
                              int lastQuoteIndex = stringToTrim.LastIndexOf( '"' );
                              while ( firstQuoteIndex != lastQuoteIndex )
                              {
                                  stringToTrim = stringToTrim.Remove( firstQuoteIndex, 1 );
                                  stringToTrim = stringToTrim.Remove( lastQuoteIndex - 1, 1 ); //-1 because we've shifted the indicies left by one
                                  firstQuoteIndex = stringToTrim.IndexOf( '"' );
                                  lastQuoteIndex = stringToTrim.LastIndexOf( '"' );
                              }
                              return stringToTrim;
                          }
                          

                          【讨论】:

                          • 我正在解决同样的问题,您可能会认为在当今时代,存在用于单元测试命令行参数字符串的简单解决方案。我要确定的是给定命令行参数字符串将产生的行为。我现在放弃了,将为 string[] 创建单元测试,但可能会添加一些集成测试来解决这个问题。
                          【解决方案23】:

                          目前,这是我拥有的代码:

                              private String[] SplitCommandLineArgument(String argumentString)
                              {
                                  StringBuilder translatedArguments = new StringBuilder(argumentString);
                                  bool escaped = false;
                                  for (int i = 0; i < translatedArguments.Length; i++)
                                  {
                                      if (translatedArguments[i] == '"')
                                      {
                                          escaped = !escaped;
                                      }
                                      if (translatedArguments[i] == ' ' && !escaped)
                                      {
                                          translatedArguments[i] = '\n';
                                      }
                                  }
                          
                                  string[] toReturn = translatedArguments.ToString().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
                                  for(int i = 0; i < toReturn.Length; i++)
                                  {
                                      toReturn[i] = RemoveMatchingQuotes(toReturn[i]);
                                  }
                                  return toReturn;
                              }
                          
                              public static string RemoveMatchingQuotes(string stringToTrim)
                              {
                                  int firstQuoteIndex = stringToTrim.IndexOf('"');
                                  int lastQuoteIndex = stringToTrim.LastIndexOf('"');
                                  while (firstQuoteIndex != lastQuoteIndex)
                                  {
                                      stringToTrim = stringToTrim.Remove(firstQuoteIndex, 1);
                                      stringToTrim = stringToTrim.Remove(lastQuoteIndex - 1, 1); //-1 because we've shifted the indicies left by one
                                      firstQuoteIndex = stringToTrim.IndexOf('"');
                                      lastQuoteIndex = stringToTrim.LastIndexOf('"');
                                  }
                                  return stringToTrim;
                              }
                          

                          它不适用于转义引号,但它适用于我目前遇到的情况。

                          【讨论】:

                            【解决方案24】:

                            【讨论】:

                            • 有用 - 但这只会让您将命令行参数发送到当前进程。要求是从字符串中获取字符串 [],“就像 C# if 在命令行上指定了命令一样”。我想我们可以使用反编译器来查看 MS 是如何实现的……
                            • Jon Galloway 还发现 (weblogs.asp.net/jgalloway/archive/2006/09/13/…) 反编译器并没有太大帮助,这让我们回到了 Atif 的答案 (stackoverflow.com/questions/298830/…)
                            【解决方案25】:

                            让我很恼火的是,没有基于检查每个字符的函数来拆分字符串的函数。如果有,你可以这样写:

                                public static IEnumerable<string> SplitCommandLine(string commandLine)
                                {
                                    bool inQuotes = false;
                            
                                    return commandLine.Split(c =>
                                                             {
                                                                 if (c == '\"')
                                                                     inQuotes = !inQuotes;
                            
                                                                 return !inQuotes && c == ' ';
                                                             })
                                                      .Select(arg => arg.Trim().TrimMatchingQuotes('\"'))
                                                      .Where(arg => !string.IsNullOrEmpty(arg));
                                }
                            

                            虽然已经写了,为什么不写必要的扩展方法。好吧,你说服了我……

                            首先,我自己的 Split 版本采用一个函数,该函数必须决定指定的字符是否应该拆分字符串:

                                public static IEnumerable<string> Split(this string str, 
                                                                        Func<char, bool> controller)
                                {
                                    int nextPiece = 0;
                            
                                    for (int c = 0; c < str.Length; c++)
                                    {
                                        if (controller(str[c]))
                                        {
                                            yield return str.Substring(nextPiece, c - nextPiece);
                                            nextPiece = c + 1;
                                        }
                                    }
                            
                                    yield return str.Substring(nextPiece);
                                }
                            

                            根据情况它可能会产生一些空字符串,但也许这些信息在其他情况下会很有用,所以我不会删除这个函数中的空条目。

                            其次(更普通的)一个小助手,它将从字符串的开头和结尾修剪一对匹配的引号。它比标准的 Trim 方法更繁琐——它只会从每一端修剪一个字符,并且不会只从一端修剪:

                                public static string TrimMatchingQuotes(this string input, char quote)
                                {
                                    if ((input.Length >= 2) && 
                                        (input[0] == quote) && (input[input.Length - 1] == quote))
                                        return input.Substring(1, input.Length - 2);
                            
                                    return input;
                                }
                            

                            我想你也需要一些测试。好吧,那好吧。但这绝对是最后一件事!首先是一个辅助函数,它将拆分的结果与预期的数组内容进行比较:

                                public static void Test(string cmdLine, params string[] args)
                                {
                                    string[] split = SplitCommandLine(cmdLine).ToArray();
                            
                                    Debug.Assert(split.Length == args.Length);
                            
                                    for (int n = 0; n < split.Length; n++)
                                        Debug.Assert(split[n] == args[n]);
                                }
                            

                            然后我可以这样写测试:

                                    Test("");
                                    Test("a", "a");
                                    Test(" abc ", "abc");
                                    Test("a b ", "a", "b");
                                    Test("a b \"c d\"", "a", "b", "c d");
                            

                            这是您的要求的测试:

                                    Test(@"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam",
                                         @"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""abcdefg@hijkl.com""", @"tasks:""SomeTask,Some Other Task""", @"-someParam");
                            

                            请注意,该实现具有额外的功能,即如果有意义,它将删除参数周围的引号(感谢 TrimMatchingQuotes 函数)。我相信这是正常命令行解释的一部分。

                            【讨论】:

                            • 我不得不取消将此标记为答案,因为我没有正确的预期输出。实际输出不应在最终数组中包含“”
                            • 我来到 Stack Overflow 是为了摆脱不断变化的需求! :) 您可以使用 Replace("\"", "") 而不是 TrimMatchingQuotes() 来删除所有引号。但是 Windows 支持 \" 以允许传递引号字符。我的 Split 函数做不到。
                            • 不错的 Earwicker :) Anton:这是我在之前的帖子中试图向您描述的解决方案,但 Earwicker 在写下它时做得更好;)并且还扩展了很多;)
                            • 空格不是命令行参数的唯一分隔符,是吗?
                            • @Louis Rhys - 我不确定。如果这是一个问题,很容易解决:使用char.IsWhiteSpace 而不是== ' '
                            【解决方案26】:

                            Windows 命令行解析器的行为就像您说的那样,在空格上拆分,除非它前面有一个未闭合的引号。我建议自己编写解析器。可能是这样的:

                                static string[] ParseArguments(string commandLine)
                                {
                                    char[] parmChars = commandLine.ToCharArray();
                                    bool inQuote = false;
                                    for (int index = 0; index < parmChars.Length; index++)
                                    {
                                        if (parmChars[index] == '"')
                                            inQuote = !inQuote;
                                        if (!inQuote && parmChars[index] == ' ')
                                            parmChars[index] = '\n';
                                    }
                                    return (new string(parmChars)).Split('\n');
                                }
                            

                            【讨论】:

                            • 我得到了同样的结果,除了我在最后一行使用了 .Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries) 以防之间有额外的 ' '参数。似乎工作正常。
                            • 我认为 Windows 必须有一种方法来转义参数中的引号......这个算法没有考虑到这一点。
                            • 删除空行、删除外部引号和处理转义引号留给读者作为练习题。
                            • Char.IsWhiteSpace() 可以在这里提供帮助
                            • 如果参数由单个空格分隔,此解决方案很好,但失败是参数由多个空格分隔。正确解决方案的链接:stackoverflow.com/a/59131568/3926504
                            猜你喜欢
                            • 2011-03-16
                            • 1970-01-01
                            • 2015-12-17
                            • 1970-01-01
                            • 2017-02-07
                            • 2017-12-10
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多