我有一个字符串,其中包含要传递给另一个可执行文件的命令行参数,我需要提取包含各个参数的字符串[],就像在命令中指定命令时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#实际生成的内容(删除了拆分字符串中的额外内容)
答案 0 :(得分:94)
令我很生气的是,根据检查每个角色的功能,没有拆分字符串的功能。如果有,你可以这样写:
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函数)。我相信这是正常命令行解释的一部分。
答案 1 :(得分:66)
除了good and pure managed solution Earwicker之外,为了完整起见,可能值得一提的是,Windows还提供了CommandLineToArgvW
函数,用于将字符串分解为数组字符串:
LPWSTR *CommandLineToArgvW( LPCWSTR lpCmdLine, int *pNumArgs);
解析Unicode命令行字符串 并返回一个指向数组 命令行参数,以及 在某种程度上,这些论点的数量 这与标准C类似 运行时argv和argc值。
从C#调用此API并在托管代码中解压缩生成的字符串数组的示例可以在“Converting Command Line String to Args[] using CommandLineToArgvW() 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);
}
}
答案 2 :(得分:23)
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');
}
答案 3 :(得分:12)
我拿了the answer from 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);
}
答案 4 :(得分:7)
good and pure managed solution Earwicker无法处理这样的参数:
Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");
它返回了3个元素:
"He whispered to her \"I
love
you\"."
所以这是一个修复,以支持&#34;引用\&#34;转义\&#34;报价&#34;:
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\".");
另请注意,使用accepted answer的Atif Aziz CommandLineToArgvW也失败了。它返回了4个元素:
He whispered to her \
I
love
you".
希望这可以帮助将来寻找这种解决方案的人。
答案 5 :(得分:4)
答案 6 :(得分:4)
我喜欢迭代器,现在LINQ使IEnumerable<String>
像字符串数组一样容易使用,所以我遵循Jeffrey L Whitledge's answer的精神(作为{{1的扩展方法) }}):
string
答案 7 :(得分:2)
这The Code Project article是我过去使用过的。这是一个很好的代码,但它可能会工作。
这个MSDN article是我能找到的解释C#解析命令行参数的唯一方法。
答案 8 :(得分:2)
因为我想要与OP相同的行为(将字符串分割成与Windows cmd完全相同的方式),所以我编写了一堆测试用例并测试了此处发布的答案:
Test( 0, m, "One", new[] { "One" });
Test( 1, m, "One ", new[] { "One" });
Test( 2, m, " One", new[] { "One" });
Test( 3, m, " One ", new[] { "One" });
Test( 4, m, "One Two", new[] { "One", "Two" });
Test( 5, m, "One Two", new[] { "One", "Two" });
Test( 6, m, "One Two", new[] { "One", "Two" });
Test( 7, m, "\"One Two\"", new[] { "One Two" });
Test( 8, m, "One \"Two Three\"", new[] { "One", "Two Three" });
Test( 9, m, "One \"Two Three\" Four", new[] { "One", "Two Three", "Four" });
Test(10, m, "One=\"Two Three\" Four", new[] { "One=Two Three", "Four" });
Test(11, m, "One\"Two Three\" Four", new[] { "OneTwo Three", "Four" });
Test(12, m, "One\"Two Three Four", new[] { "OneTwo Three Four" });
Test(13, m, "\"One Two\"", new[] { "One Two" });
Test(14, m, "One\" \"Two", new[] { "One Two" });
Test(15, m, "\"One\" \"Two\"", new[] { "One", "Two" });
Test(16, m, "One\\\" Two", new[] { "One\"", "Two" });
Test(17, m, "\\\"One\\\" Two", new[] { "\"One\"", "Two" });
Test(18, m, "One\"", new[] { "One" });
Test(19, m, "\"One", new[] { "One" });
Test(20, m, "One \"\"", new[] { "One", "" });
Test(21, m, "One \"", new[] { "One", "" });
Test(22, m, "1 A=\"B C\"=D 2", new[] { "1", "A=B C=D", "2" });
Test(23, m, "1 A=\"B \\\" C\"=D 2", new[] { "1", "A=B \" C=D", "2" });
Test(24, m, "1 \\A 2", new[] { "1", "\\A", "2" });
Test(25, m, "1 \\\" 2", new[] { "1", "\"", "2" });
Test(26, m, "1 \\\\\" 2", new[] { "1", "\\\"", "2" });
Test(27, m, "\"", new[] { "" });
Test(28, m, "\\\"", new[] { "\"" });
Test(29, m, "'A B'", new[] { "'A", "B'" });
Test(30, m, "^", new[] { "^" });
Test(31, m, "^A", new[] { "A" });
Test(32, m, "^^", new[] { "^" });
Test(33, m, "\\^^", new[] { "\\^" });
Test(34, m, "^\\\\", new[] { "\\\\" });
Test(35, m, "^\"A B\"", new[] { "A B" });
// Test cases Anton
Test(36, m, @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo", new[] { @"/src:C:\tmp\Some Folder\Sub Folder", @"/users:abcdefg@hijkl.com", @"tasks:SomeTask,Some Other Task", @"-someParam", @"foo" });
// Test cases Daniel Earwicker
Test(37, m, "", new string[] { });
Test(38, m, "a", new[] { "a" });
Test(39, m, " abc ", new[] { "abc" });
Test(40, m, "a b ", new[] { "a", "b" });
Test(41, m, "a b \"c d\"", new[] { "a", "b", "c d" });
// Test cases Fabio Iotti
Test(42, m, "this is a test ", new[] { "this", "is", "a", "test" });
Test(43, m, "this \"is a\" test", new[] { "this", "is a", "test" });
// Test cases Kevin Thach
Test(44, m, "\"C:\\Program Files\"", new[] { "C:\\Program Files" });
Test(45, m, "\"He whispered to her \\\"I love you\\\".\"", new[] { "He whispered to her \"I love you\"." });
“期望”值来自在我的计算机(Win10 x64)和一个简单的打印程序上使用cmd.exe直接对其进行测试:
static void Main(string[] args) => Console.Out.WriteLine($"Count := {args.Length}\n{string.Join("\n", args.Select((v,i) => $"[{i}] => '{v}'"))}");
这些是结果:
Solution | Failed Tests
------------------------------|-------------------------------------
Atif Aziz (749653) | 2, 3, 10, 11, 12, 14, 16, 17, 18, 26, 28, 31, 32, 33, 34, 35, 36, 37, 39, 45
Jeffrey L Whitledge (298968) | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Daniel Earwicker (298990) | 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 45
Anton (299795) | 12, 16, 17, 18, 19, 21, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
CS. (467313) | 12, 18, 19, 21, 27, 31, 32, 33, 34, 35
Vapour in the Alley (2132004) | 10, 11, 12, 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 45
Monoman (7774211) | 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
Thomas Petersson (19091999) | 2, 3, 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 39, 45
Fabio Iotti (19725880) | 1, 2, 3, 7, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 22, 23, 25, 26, 28, 29, 30, 35, 36, 37, 39, 40, 42, 44, 45
ygoe (23961658) | 26, 31, 32, 33, 34, 35
Kevin Thach (24829691) | 10, 11, 12, 14, 18, 19, 20, 21, 22, 23, 26, 27, 31, 32, 33, 34, 35, 36
Lucas De Jesus (31621370) | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45
HarryP (48008872) | 24, 26, 31, 32, 33, 34, 35
TylerY86 (53290784) | 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 41, 43, 44, 45
Louis Somers (55903304) | 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 39, 41, 43, 44, 45
user2126375 (58233585) | 5, 6, 15, 16, 17, 31, 32, 33, 34, 35
DilipNannaware (59131568) | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Mikescher (this) | -
因为没有答案似乎是正确的(至少基于我的用例),这是我的解决方案,所以它当前通过了所有测试用例(但是如果有人还有其他(失败)极端情况,请发表评论):
public static IEnumerable<string> SplitArgs(string commandLine)
{
var result = new StringBuilder();
var quoted = false;
var escaped = false;
var started = false;
var allowcaret = false;
for (int i = 0; i < commandLine.Length; i++)
{
var chr = commandLine[i];
if (chr == '^' && !quoted)
{
if (allowcaret)
{
result.Append(chr);
started = true;
escaped = false;
allowcaret = false;
}
else if (i + 1 < commandLine.Length && commandLine[i + 1] == '^')
{
allowcaret = true;
}
else if (i + 1 == commandLine.Length)
{
result.Append(chr);
started = true;
escaped = false;
}
}
else if (escaped)
{
result.Append(chr);
started = true;
escaped = false;
}
else if (chr == '"')
{
quoted = !quoted;
started = true;
}
else if (chr == '\\' && i + 1 < commandLine.Length && commandLine[i + 1] == '"')
{
escaped = true;
}
else if (chr == ' ' && !quoted)
{
if (started) yield return result.ToString();
result.Clear();
started = false;
}
else
{
result.Append(chr);
started = true;
}
}
if (started) yield return result.ToString();
}
我用来生成测试结果的代码可以找到here
答案 9 :(得分:1)
哦,哎呀。都是...呃。 But this is legit official.来自Microsoft,使用C#for .NET Core,也许只有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
的猴子的百分比相当。
答案 10 :(得分:1)
纯托管解决方案可能会有所帮助。 WINAPI函数有太多“问题”注释,并且在其他平台上不可用。这是我的代码具有明确定义的行为(如果您愿意,可以更改)。
它应该与.NET / Windows在提供string[] args
参数时所做的相同,并且我将它与一些“有趣”的值进行了比较。
这是一个经典的状态机实现,它从输入字符串中获取每个单个字符并将其解释为当前状态,从而产生输出和新状态。状态在变量escape
,inQuote
,hadQuote
和prevCh
中定义,输出收集在currentArg
和args
中。< / p>
我在实际命令提示符(Windows 7)上通过实验发现的一些专业:\\
生成\
,\"
生成"
,{{引号范围内的1}}会生成""
。
"
角色似乎也是神奇的:当它不加倍时它总会消失。否则它对实际命令行没有影响。我的实现不支持这个,因为我没有在这种行为中找到模式。也许有人知道更多。
不适合此模式的是以下命令:
^
cmd /c "argdump.exe "a b c""
命令似乎抓住外部引号并逐字记录其余部分。这里必定有一些特殊的魔法酱。
我的方法没有基准,但考虑得相当快。它不使用cmd
并且不执行任何字符串连接,而是使用Regex
来收集参数的字符并将它们放在列表中。
StringBuilder
答案 11 :(得分: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' });
}
根据Vapour in the Alley的回答,这个也支持^ escapes。
示例:
它还支持多个空格(每个空格块只打破一次参数)。
答案 12 :(得分:1)
在你的问题中,你要求一个正则表达式,我是他们的忠实用户,所以当我需要像你一样做同样的论点时,我在google搜索后写了我自己的正则表达式而没有找到一个简单的解决方案。我喜欢简短的解决方案,所以我做了一个,这里是:
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();
它处理引号内的空格和引号,并将随附的“”转换为“。随意使用代码!
答案 13 :(得分: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();
}
}
答案 14 :(得分: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');
}
答案 15 :(得分:0)
有一个NuGet软件包,其中包含您所需的功能:
Microsoft.CodeAnalysis.Common包含类CommandLineParser和方法SplitCommandLineIntoArguments。
您可以这样使用它:
using Microsoft.CodeAnalysis;
// [...]
var cli = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo";
var cliArgs = CommandLineParser.SplitCommandLineIntoArguments(cli, true);
Console.WriteLine(string.Join('\n', cliArgs));
// prints out:
// /src:"C:\tmp\Some Folder\Sub Folder"
// /users:"abcdefg@hijkl.com"
// tasks:"SomeTask,Some Other Task"
// -someParam
// foo
答案 16 :(得分:0)
在这里找不到我喜欢的东西。我不喜欢用屈服魔力将堆栈弄乱,成为一个小的命令行(如果它是TB级的流,那将是另一个故事)。
这是我的看法,它支持双引号这样的引号转义:
param =“ a 15”“屏幕还不错” param2 ='a 15“屏幕还不错'param3 =”“ param4 = / param5
结果:
param =“一个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();
}
答案 17 :(得分:0)
我编写了一个方法来将文件名与其参数分开,用于需要将文件名和参数字符串分开的 ProcessStartInfo
。
例如"C:\Users\Me\Something.exe" -a
结果会给出{ "C:\Users\Me\Something.exe", "-a" }
代码如下:
public static string[] SplitCommandFromArgs(string commandLine)
{
commandLine = commandLine.Trim();
if (commandLine[0] == '"')
{
bool isEscaped = false;
for (int c = 1; c < commandLine.Length; c++)
{
if (commandLine[c] == '"' && !isEscaped)
{
return new string[] { commandLine.Substring(1, c - 1), commandLine.Substring(c + 1).Trim() };
}
isEscaped = commandLine[c] == '\\';
}
}
else
{
for (int c = 1; c < commandLine.Length; c++) {
if (commandLine[c] == ' ')
{
return new string[] { commandLine.Substring(0, c), commandLine.Substring(c).Trim() };
}
}
}
return new string[] { commandLine, "" };
}
答案 18 :(得分: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();
}
答案 19 :(得分:0)
这是一个完成工作的单线程(参见执行BurstCmdLineArgs(...)方法中所有工作的一行)。
不是我称之为最易读的代码行,但为了便于阅读,您可以将其分解出来。它的目的很简单,并不适用于所有参数情况(如文件名参数中包含拆分字符串字符分隔符)。
此解决方案在我使用它的解决方案中运行良好。就像我说的那样,它完成了工作,没有一个代码嵌套来处理每个可能的参数格式n-factorial。
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;
}
}
}
答案 20 :(得分: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;
}
用葡萄牙语写的。
答案 21 :(得分:0)
您可以查看我昨天发布的代码:
它将文件名+参数拆分为string []。处理短路径,环境变量和丢失的文件扩展名。
(最初是注册表中的UninstallString。)
答案 22 :(得分:0)
这是对Anton的代码的回复,该代码不适用于转义引号。我修改了3个地方。
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;
}
答案 23 :(得分: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;
}
它不适用于转义引号,但它适用于我到目前为止遇到的情况。
答案 24 :(得分:-2)
我不确定我是否理解你,但是在文本中也可以找到角色用作分割器的问题? (除了它是双重逃脱?)
如果是这样,我会创建一个for
循环,并将&lt;“&gt;所在的所有实例替换为&lt; |&gt;(或其他”安全“字符,但请确保它仅替换&lt;“&gt;,而不是&lt;”“&gt;
在迭代字符串后,我会像之前发布的那样,分割字符串,但现在在字符&lt; |&gt;上。
答案 25 :(得分:-6)
是的,字符串对象有一个名为Split()
的内置函数,它接受一个参数,指定要作为分隔符查找的字符,并返回一个字符串数组(string []),其中包含各个值它