我有一个字符串,其中包含要传递给另一个可执行文件的命令行参数,我需要提取包含各个参数的字符串[],方法与在命令行中指定命令时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#实际生成的内容(删除了拆分字符串中的额外")
令我很生气的是,根据检查每个角色的功能,没有拆分字符串的功能.如果有,你可以像这样写:
public static IEnumerableSplitCommandLine(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 IEnumerableSplit(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函数).我相信这是正常命令行解释的一部分.
除了善良纯洁的托管解决方案由埃里克,它可能是值得一提的,为了完整起见,该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); } }
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'); }
我从杰弗里·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); }
本善良纯洁的托管解决方案由埃里克没能处理这样的论点:
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 IEnumerableSplitCommandLine(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".
希望这可以帮助将来寻找这种解决方案的人.