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


88

我有一个包含要传递给另一个可执行文件的命令行参数的字符串,并且我需要以与C#如果在命令行上指定命令相同的方式提取包含单个参数的string []。通过反射执行另一个程序集入口点时,将使用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#实际生成的内容(删除了拆分字符串中多余的“”)



5
每次有人回应时,您似乎都会根据您帖子中没有的材料提出异议。我建议您使用此材料更新您的帖子。您可能会得到更好的答案。
tvanfosson

1
好问题,寻找相同的。希望能找到一个说“嘿.net在这里公开...”的人:)如果我在某个时候遇到了这个问题,即使它已经6岁了,我也将其张贴在这里。仍然是一个有效的问题!
MikeJansen 2014年

我也在下面的答案中创建了一个纯托管版本,因为我也需要此功能。
ygoe 2014年

Answers:


74

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

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

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

在“ 使用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);
    }
}

1
此功能要求您在引号内转义路径的尾部反斜杠。“ C:\ Program Files \”必须为“ C:\ Program Files \\”,这样才能正确解析字符串。
Magnus Lindhe,2009年

8
还值得注意的是CommandLineArgvW期望第一个参数是程序名称,并且如果没有传入,则应用的解析魔术也不完全相同。您可以使用以下方式进行伪造:CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
Scott Wegner

4
为了完整起见,MSVCRT不使用CommandLineToArgvW()将命令行转换为argc / argv。它使用自己的代码,这是不同的。例如,尝试使用以下字符串调用CreateProcess:a“ b c” def。在main()中,您将获得3个参数(如MSDN中所述),但CommandLineToArgvW()/ GetCommandLineW()组合将为您提供2。–
LRN

7
天哪,这真是一团糟。典型的MS汤。没有任何事情可以规范化,在MS世界中从不尊重KISS。
v.oddou

1
我发布了Microsoft翻译的MSVCRT实现的跨平台版本,以及使用Regex进行的高精度近似。我知道这很旧,但是嘿-没有身体滚动。
TylerY86 '19

101

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

    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函数)。我相信这是正常命令行解释的一部分。


我没有将其标记为答案,因为我没有正确的预期输出。最终输出中的实际输出不应包含“”
Anton

16
我来Stack Overflow是为了摆脱一直在变化的需求!:)您可以使用Replace(“ \”“,”“)代替TrimMatchingQuotes()来消除所有引号。但是Windows支持\”允许使用引号字符。我的拆分功能无法做到这一点。
Daniel Earwicker's

1
不错的一个Earwicker :) Anton:这是我在较早的帖子中试图向您描述的解决方案,但是Earwicker在写下来方面做得更好;)并且还对其进行了扩展;)
Israr Khan

空格不是命令行参数的唯一分隔符,对吗?
路易·莱斯

@Louis Rhys-我不确定。如果这是一个问题,那么很容易解决:使用char.IsWhiteSpace代替== ' '
Daniel Earwicker 2010年

25

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

2
最后我做了同样的事情,很可笑,我在最后一行使用了.Split(new char [] {'\ n'},StringSplitOptions.RemoveEmptyEntries),以防参数之间有多余的'。似乎正在工作。
安东

3
我认为Windows必须有一种在参数中转义引号的方法...此算法未考虑到这一点。
rmeador

对于读者来说,删除空白行,删除外部引号以及处理转义的引号是一种最佳选择。
Jeffrey L Whitledge,

Char.IsWhiteSpace()可以为您提供帮助
Sam Mackrill

如果参数由单个空格分隔,则此解决方案很好,但如果参数由多个空格分隔,则失败。链接到正确的解决方案:stackoverflow.com/a/59131568/3926504
Dilip Nannaware

13

从杰弗里·惠特里奇(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);
    }

7

善良纯洁的托管解决方案埃里克未能处理参数是这样的:

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

它返回了3个元素:

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

因此,这里有一个修复程序来支持“ quoted \“ escape \” quote”:

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\".");

还指出,接受的答案通过与Atif阿齐兹它采用CommandLineToArgvW也失败了。它返回了4个元素:

He whispered to her \ 
I 
love 
you". 

希望这对将来寻求这种解决方案的人有所帮助。


3
对不起,我很抱歉,但是该解决方案仍然遗漏了诸如此类的bla.exe aAAA"b\"ASDS\"c"dSADSD结果,导致aAAAb"ASDS"cdSADSD该解决方案可以输出到何处aAAA"b"ASDS"c"dSADSD。我可能会考虑将更TrimMatchingQuotes改为a Regex("(?<!\\\\)\\\"")像这样使用它 。
Scis 2016年

4

2
有用-但这只会使您将命令行args发送到当前进程。要求是从字符串中获取string [],“ 如果在命令行上指定了命令,则与C#的方式相同”。我想我们可以使用反编译器来查看MS如何实现此目标……
rohancragg 2011年

正如乔恩·加洛韦(Jon Galloway)所发现的(weblogs.asp.net/jgalloway/archive/2006/09/13/…)一样,反编译器也无济于事,这使我们直接回到了Atif的答案(stackoverflow.com/questions/298830/…
rohancragg

4

我喜欢迭代器,如今LINQ使得它IEnumerable<String>像字符串数组一样容易使用,因此我遵循Jeffrey L Whitledge的回答的精神是(作为的扩展方法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();
}

3

在您的问题中,您要求使用正则表达式,而我是它们的忠实拥护者和用户,因此当我需要与您进行相同的参数拆分时,我在四处搜寻并且没有找到简单的解决方案后编写了自己的正则表达式。我喜欢简短的解决方案,因此我提出了一个解决方案:

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

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


3

哎呀 都是...呃。但这是合法官员。来自Microsoft,适用于.NET Core的C#语言,也许只有Windows,也许是跨平台的,但是MIT许可。

选择花絮,方法声明和引人注目的注释;

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




1
限时复兴。pastebin.com/ajhrBS4t
TylerY86

2

这篇代码项目文章是我过去使用的。这是一段不错的代码,但可能会起作用。

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


我尝试了将反射器放入C#库中,但是它转到了我自己没有代码的C ++本地调用,并且在没有p调用的情况下看不到任何调用方式。我也不需要命令行解析库,只需要string []。
安东

反映.NET也无济于事。查看Mono 代码 表明,此参数拆分不是由CLR完成的,而是已经来自操作系统的。想想C主函数的argc,argv参数。因此,除了OS API外,没有其他可重用的东西。
ygoe 2014年

1

一个纯粹的管理解决方案可能会有所帮助。WINAPI函数的“问题”注释过多,在其他平台上不可用。这是我的代码,具有明确定义的行为(可以根据需要更改)。

提供该string[] args参数时,它的作用应与.NET / Windows相同。我已将其与许多“有趣”的值进行了比较。

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

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

^字符似乎是不可思议,太:它总是在不增加一倍消失。否则,它对实际命令行无效。我的实现不支持此操作,因为我没有在这种行为中找到任何模式。也许有人对此有更多了解。

以下命令不适合此模式:

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

cmd命令似乎抓住了外部引号,并逐字逐句地处理了其余部分。里面一定有一些特殊的魔术酱。

我尚未对自己的方法进行任何基准测试,但认为它相当快。它不使用Regex也不进行任何字符串连接,而是使用a 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();
}

1

用:

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

根据Alley回答中的Vapor,该代码还支持^转义。

例子:

  • 这是一个测验
    • 这个
    • 一个
    • 测试
  • 这是一个测验
    • 这个
    • 是一个
    • 测试
  • 这个^“是一个^”测试
    • 这个
    • “是
    • 一个”
    • 测试
  • 这个“”是^^测试
    • 这个
    • 是^测试

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


这三个中的最后一个以某种方式干扰了Markdown并且未按预期呈现。
彼得·莫滕森

固定为零宽度空间。
法比奥·伊奥蒂

0

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

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

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


0

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

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

我正在解决同一问题,您可能会认为,在当今时代,将存在一种用于单元测试命令行参数字符串的简单解决方案。我只想确定的是,给定命令行参数字符串将导致的行为。我暂时放弃,将为string []创建单元测试,但可能会添加一些集成测试来解决这一问题。
查理·巴克

0

我认为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();
}


0

试试这个代码:

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

它是用葡萄牙语写的。


相当的文档是葡萄牙语
Enamul Hassan

@EnamulHassan我想说代码也是葡萄牙语的,例如posicao_ponteiro += ((fim - posicao_ponteiro)+1);
MEMark

0

这是一个可以完成工作的衬板(请参阅在BurstCmdLineArgs(...)方法内完成所有工作的一行)。

不是我所说的最易读的代码行,但是出于可读性考虑,您可以将其分开。这是有目的的,并且不适用于所有参数情况(例如文件名参数中包含分割字符串分隔符)。

此解决方案在使用它的我的解决方案中效果很好。就像我说的那样,它可以完成工作,而无需花费太多的代码来处理所有可能的n阶参数格式。

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

0

在这里找不到我喜欢的东西。我不喜欢用屈服魔法将堆栈弄乱,成为一个小的命令行(如果它是TB级的流,那将是另一个故事)。

这是我的看法,它支持双引号这样的引号转义:

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

结果:

param =“ a 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();
}

0

我已经实现了状态机,使其具有与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();
        }
    }

0

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

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

-2

我不确定我是否理解您,但是是否还会在文本内找到用作分隔符的字符的问题?(除了用双“?”转义之外)

如果是这样,我将创建一个for循环,并用<|>(或另一个“安全”字符)替换存在<“>的所有实例,但请确保仅替换<”>,而不替换<“”>

迭代字符串后,我将像以前发布的那样进行操作,拆分字符串,但是现在在字符<|>上。


双“”是因为它是一个@“ ..”字符串文字,双“”在@“ ..”字符串内相当于一个普通字符串中的\“逸出了”
Anton

“唯一的限制(我相信)是字符串是用空格分隔的,除非空格出现在“ ...”块内“->可能会用火箭筒射击小鸟,但要放一个布尔值,它会变成” true“在引号内,并且如果在“ true”内检测到空格,则继续,否则<> = <|>
Israr Khan

-6

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


1
这将错误地分割src:“ C:\ tmp \ Some Folder \ Sub Folder”部分。
安东

字符串中暂时关闭空格分割的引号怎么办?
Daniel Earwicker's
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.