在 C# 中将包含命令行参数的字符串拆分为 string[]

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/298830/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 22:11:42  来源:igfitidea点击:

Split string containing command-line parameters into string[] in C#

c#command-linetext-parsing

提问by Anton

I have a single string that contains the command-line parameters to be passed to another executable and I need to extract the string[] containing the individual parameters in the same way that C# would if the commands had been specified on the command-line. The string[] will be used when executing another assemblies entry-point via reflection.

我有一个包含要传递给另一个可执行文件的命令行参数的字符串,我需要以与 C# 相同的方式提取包含各个参数的 string[],如果在命令行上指定了命令。通过反射执行另一个程序集入口点时将使用 string[]。

Is there a standard function for this? Or is there a preferred method (regex?) for splitting the parameters correctly? It must handle '"' delimited strings that may contain spaces correctly, so I can't just split on ' '.

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

Example string:

示例字符串:

string parameterString = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam foo";

Example result:

结果示例:

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

I do not need a command-line parsing library, just a way to get the String[] that should be generated.

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

Update: I had to change the expected result to match what is actually generated by C# (removed the extra "'s in the split strings)

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

采纳答案by Atif Aziz

In addition to the good and pure managed solutionby Earwicker, it may be worth mentioning, for sake of completeness, that Windows also provides the CommandLineToArgvWfunction for breaking up a string into an array of strings:

除了善良纯洁的托管解决方案埃里克,它可能是值得一提的,为了完整起见,该Windows还提供了CommandLineToArgvW分手字符串转换成字符串数组功能:

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

Parses a Unicode command line string and returns an array of pointers to the command line arguments, along with a count of such arguments, in a way that is similar to the standard C run-time argv and argc values.

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

解析 Unicode 命令行字符串并返回指向命令行参数的指针数组以及此类参数的计数,其方式类似于标准 C 运行时 argv 和 argc 值。

An example of calling this API from C# and unpacking the resulting string array in managed code can be found at, “Converting Command Line String to Args[] using CommandLineToArgvW() API.” Below is a slightly simpler version of the same code:

在“使用 CommandLineToArgvW() API 将命令行字符串转换为 Args[]”中可以找到从 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);
    }
}

回答by Charles Bretana

Yes, the string object has a built in function called Split()that takes a single parameter specifying the character to look for as a delimiter, and returns an array of strings (string[]) with the individual values in it.

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

回答by Israr Khan

I am not sure if I understood you, but is the problem that the character used as splitter, is also to be found inside the text? (Except for that it is escaped with double "?)

我不确定我是否理解你,但是用作分隔符的字符是否也存在于文本中的问题?(除此之外,它用双“?”转义了?)

If so, I would create a forloop, and replace all instances where <"> is present with <|> (or another "safe" character, but make sure that it only replaces <">, and not <"">

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

After iterating the string, I would do as previously posted, split the string, but now on the character <|>.

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

回答by Zachary Yates

This The Code Project articleis what I've used in the past. It's a good bit of code, but it might work.

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

This MSDN articleis the only thing I could find that explains how C# parses command line arguments.

这篇MSDN 文章是我能找到的唯一解释 C# 如何解析命令行参数的文章。

回答by Jeffrey L Whitledge

The Windows command-line parser behaves just as you say, split on space unless there's a unclosed quote before it. I would recommend writing the parser yourself. Something like this maybe:

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');
    }

回答by Daniel Earwicker

It annoys me that there's no function to split a string based on a function that examines each character. If there was, you could write it like this:

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

    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));
    }

Although having written that, why not write the necessary extension methods. Okay, you talked me into it...

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

Firstly, my own version of Split that takes a function that has to decide whether the specified character should split the string:

首先,我自己的 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);
    }

It may yield some empty strings depending on the situation, but maybe that information will be useful in other cases, so I don't remove the empty entries in this function.

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

Secondly (and more mundanely) a little helper that will trim a matching pair of quotes from the start and end of a string. It's more fussy than the standard Trim method - it will only trim one character from each end, and it will not trim from just one end:

其次(更常见的是)一个小助手,它将从字符串的开头和结尾修剪一对匹配的引号。它比标准的 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;
    }

And I suppose you'll want some tests as well. Well, alright then. But this must be absolutely the last thing! First a helper function that compares the result of the split with the expected array contents:

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

    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]);
    }

Then I can write tests like this:

然后我可以写这样的测试:

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

Here's the test for your requirements:

以下是针对您的要求的测试:

        Test(@"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam",
             @"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""[email protected]""", @"tasks:""SomeTask,Some Other Task""", @"-someParam");

Note that the implementation has the extra feature that it will remove quotes around an argument if that makes sense (thanks to the TrimMatchingQuotes function). I believe that's part of the normal command-line interpretation.

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

回答by Anton

Currently, this is the code that I have:

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

    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;
    }

It doesn't work with escaped quotes, but it works for the cases that I've come up against so far.

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

回答by CS.

This is a reply to Anton's code, which do not work with escaped quotes. I modified 3 places.

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

  1. The constructorfor StringBuilderin SplitCommandLineArguments, replacing any \"with \r
  2. In the for-loopin SplitCommandLineArguments, I now replace the \rcharacter back to \".
  3. Changed the SplitCommandLineArgumentmethod from privateto public static.
  1. 构造函数用于StringBuilder的SplitCommandLineArguments,替换\”\ r
  2. SplitCommandLineArgumentsfor 循环中,我现在将\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;
}

回答by Vapour in the Alley

I took the answer from Jeffrey L Whitledgeand enhanced it a little.

从 Jeffrey L Whitledge 那里得到了答案,并对其进行了一些改进。

It now supports both single and double quotes. You can use quotes in the parameters itself by using other typed quotes.

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

It also strips the quotes from the arguments since these do not contribute to the argument information.

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

    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);
    }