将包含命令行参数的string拆分为C#中的string
我有一个包含要传递给另一个可执行文件的命令行参数的单个string,我需要提取包含单个参数的string[],如果在命令行中指定了命令,那么C#将采用相同的方式。 当通过reflection执行另一个组件入口点时,将使用string[]。
有这个标准的function吗? 还是有一个首选的方法(正则expression式?)正确分裂参数? 它必须处理可能包含正确空格的'''分隔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" };
我不需要命令行parsing库,只是获取应该生成的String []的一种方法。
更新 :我不得不改变预期的结果,以匹配实际上由C#生成(删除多余的“在拆分string中的)
除了Earwicker提供的良好且纯粹的托pipe解决scheme 外 ,为了完整起见,值得一提的是,Windows还提供了将string分解为string数组的CommandLineToArgvW
函数:
LPWSTR *CommandLineToArgvW( LPCWSTR lpCmdLine, int *pNumArgs);
parsing一个Unicode命令行string,并以类似于标准C运行时argv和argc值的方式,返回一个指向命令行参数的指针数组,以及此类参数的计数。
从C#中调用这个API并在托pipe代码中解压得到的string数组的例子可以在“ 使用CommandLineToArgvW()API将命令行string转换为Args [] ”中find。下面是相同代码的稍微简单的版本:
[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); } }
它让我很烦恼,没有函数根据检查每个字符的函数来分割string。 如果有的话,你可以这样写:
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版本需要一个函数来决定指定的字符是否应该拆分string:
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); }
它可能会产生一些空string取决于情况,但也许这些信息将在其他情况下有用,所以我不删除这个函数中的空条目。
其次(也是更平常的),一个小帮手,将从一个string的开始和结尾修剪一对匹配的引号。 它比标准修剪方法更挑剔 – 它只会从每一端修剪一个字符,而不会从一端修剪:
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; }
而且我想你也需要一些testing。 那好吧。 但这绝对是最后一件事! 首先是一个帮助函数,将分割的结果与期望的数组内容进行比较:
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]); }
然后我可以写这样的testing:
Test(""); Test("a", "a"); Test(" abc ", "abc"); Test("ab ", "a", "b"); Test("ab \"cd\"", "a", "b", "cd");
这是您的要求的testing:
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");
请注意,该实现具有额外的function,它将删除引号周围的引号,如果这是有道理的(感谢TrimMatchingQuotes函数)。 我相信这是正常的命令行解释的一部分。
Windows命令行parsing器的行为就像你说的那样,在空间上分割,除非在它之前有一个未封闭的引用。 我会build议你自己写parsing器。 这样的事情可能是:
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'); }
我从Jeffrey L Whitledge那里得到了答案,并加强了一点。 我还没有足够的信用来评论他的答案。
它现在支持单引号和双引号。 您可以使用其他types的引号在参数中使用引号。
它也从参数中去掉了引号,因为这些引用没有参数信息。
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); }
Environment.GetCommandLineArgs()
谷歌说: C#/ .NET命令行参数parsing器
由Earwicker提供的良好且纯粹的托pipe解决scheme未能处理这样的争论:
Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");
它返回了三个元素:
"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)); }
testing了另外两个案例:
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".
希望这可以帮助有人在未来寻找这样的解决scheme。
这个代码项目文章是我以前使用的,这是一个很好的代码,但它可能工作。
这个MSDN文章是我能find的唯一解释C#分析命令行参数的唯一方法。
希望有所帮助!
我喜欢迭代器,现在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(); }
在你的问题中,你问了一个正则expression式,我是他们的粉丝和用户,所以当我需要和你一样分裂这个论点时,我用googlesearch了一下自己的正则expression式,却找不到一个简单的解决scheme。 我喜欢短的解决scheme,所以我做了一个,这里是:
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();
它处理引号内的空格和引号,并将封闭的“”转换为“。随意使用代码!
我知道这是旧的,但有人可能会发现一个纯粹的pipe理解决scheme有帮助。 WINAPI函数有太多的“问题”注释,并且在其他平台上不可用。 这里是我的代码,有一个明确的行为(如果你喜欢,你可以改变)。 它应该与.NET / Windows在提供string[] args
参数时所做的一样,我已经将它与一些“有趣的”值进行了比较。
这是一个经典的状态机实现,它从inputstring中获取每个单个字符并将其解释为当前状态,从而产生输出和新状态。 状态在variablesescape
, inQuote
, hadQuote
和prevCh
,输出被收集在prevCh
和args
。
我在实际的命令提示符(Windows 7)上通过实验发现的一些特色: \\
\
在引用范围内产生"
\"
, \"
产生"
, ""
"
。
^
字符似乎也是不可思议的:当它没有加倍时,它总是消失。 否则它不会影响真正的命令行。 我的实现不支持这个,因为我还没有发现这种行为模式。 也许有人知道更多关于它。
一些不适合这种模式的是以下命令:
cmd /c "argdump.exe "abc""
cmd
命令似乎可以捕捉外部引用,并逐字逐句执行。 这里一定有一些特别的魔法酱。
我没有对我的方法做任何基准testing,但考虑一下速度相当快。 它不使用正则Regex
,不做任何string连接,而是使用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(); }
目前,这是我有的代码:
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; }
它不适用于逃脱的引号,但它适用于我迄今为止遇到的情况。
这是对Anton的代码的回复,它不适用于逃脱的引号。 我修改了3个地方。
- SplitCommandLineArguments中 StringBuilder的构造函数 ,用\ rreplace\“
- 在SplitCommandLineArguments的for循环中,我现在将\ r字符replace回\“ 。
- 将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; }
你可以看看我昨天发布的代码:
http://social.msdn.microsoft.com/Forums/fr-FR/netfx64bit/thread/2dfe45f5-7940-48cd-bd57-add8f3d94102
它将一个文件名+参数分割成string[]。 短path,环境variables,丢失的文件扩展名被处理。
(最初是在registry中的UninstallString)。
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 ,这个也支持^ escapes
例子:
- 这是一个testing
- 这个
- 是
- 一个
- testing
- 这是一个testing
- 这个
- 是一个
- testing
- 这个^“是一个^”testing
- 这个
- “是
- 一个”
- testing
- 这个“”是“^^testing”
- 这个
- 是一个^testing
也支持多个空格(每个空格块只占用一次空间)
试试这个代码:
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; }
这是用葡萄牙语写的。
这是一个完成工作的单线程(请参阅执行BurstCmdLineArgs(…)方法中所有工作的一行)。 不是我所说的最可读的代码行,但是出于可读性的原因,可以将其分解出来。 它的目的很简单,并不适用于所有的参数情况(如文件名参数中包含分隔string字符分隔符)。 这个解决scheme在我使用它的解决scheme中运行良好。 就像我说的那样,它完成了这个工作,没有老鼠的代码来处理每个可能的参数格式。
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". // Eg -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; } } }
不知道我是否理解你,但是用作分离器的字符的问题也可以在文本中find? (除了它是双重逃脱?)
如果是这样的话,我会创build一个for循环,并用<|>(或另一个“安全”字符replace<>>中的所有实例,但确保它只replace<“>,而不是<”>
在迭代string之后,我会像之前发布的那样执行拆分string,但是现在在字符<|>上
编辑:为了读起来,我添加了,即“写为”,因为当我只写“”和“或”
是的,string对象有一个名为Split()的内置函数,它接受一个单独的参数来指定要查找的字符作为分隔符,并返回一个string数组(string [])