当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《在C#中将包含命令行参数的字符串拆分为字符串[]》经验,为你挑选了5个好方法。

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

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

示例字符串:

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#实际生成的内容(删除了拆分字符串中的额外")



1> Daniel Earwi..:

令我很生气的是,根据检查每个角色的功能,没有拆分字符串的功能.如果有,你可以像这样写:

    public static IEnumerable 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 Split(this string str, 
                                            Func 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来摆脱一直变化的需求!:)您可以使用替换("\"","")而不是TrimMatchingQuotes()来删除所有引号.但Windows支持\"允许引用字符传递.我的分割功能无法做到这一点.

2> Atif Aziz..:

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

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

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

从C#调用此API并在托管代码中解压缩生成的字符串数组的示例可以在" 使用CommandLineToArgvW()API将命令行字符串转换为Args [ "中找到."下面是相同代码的稍微简单版本:

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


天啊这是一团糟.典型的MS汤.没有什么是规范化的,从来没有KISS在MS世界中受到尊重.
还值得注意的是CommandLineArgvW期望第一个参数是程序名称,并且如果没有传入,则应用的解析魔法也不完全相同.你可以用以下内容伪造它:`CommandLineToArgs("foo.exe "+ commandLine".Skip(1).ToArray();`
为了完整起见,MSVCRT不使用CommandLineToArgvW()将命令行转换为argc/argv.它使用自己的代码,这是不同的.例如,尝试使用此字符串调用CreateProcess:a"b c"def.在main()中,您将获得3个参数(如MSDN中所述),但CommandLineToArgvW()/ GetCommandLineW()组合将为您提供2个参数.

3> Jeffrey L Wh..:

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),以防params之间有额外的'.似乎工作.
我假设Windows必须有一种方法来逃避参数中的引号...这个算法没有考虑到这一点.

4> 小智..:

我从杰弗里·L·惠特利奇那里得到了答案,并对其进行了一些改进.

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

它还从参数中删除引号,因为它们不会参与参数信息.

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



5> 小智..:

本善良纯洁的托管解决方案由埃里克没能处理这样的论点:

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

还注意到使用CommandLineToArgvW的Atif Aziz 接受的答案也失败了.它返回了4个元素:

He whispered to her \ 
I 
love 
you". 

希望这可以帮助将来寻找这种解决方案的人.


很抱歉这个解决方案,但这个解决方案仍然错过像'bla.exe aAAA"b \"ASDS \"c"dSADSD`这样的结果是'aAAAb"ASDS"cdSADSD`,其中此解决方案将输出`aAAA"b"ASDS"c "dSADSD`.我可能会考虑将`TrimMatchingQuotes`改为`正则表达式("?
推荐阅读
手机用户2502852037
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有