以下行为是C#.NET中的某些功能还是错误?
测试应用程序:
using System;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Arguments:");
foreach (string arg in args)
{
Console.WriteLine(arg);
}
Console.WriteLine();
Console.WriteLine("Command Line:");
var clArgs = Environment.CommandLine.Split(' ');
foreach (string arg in clArgs.Skip(clArgs.Length - args.Length))
{
Console.WriteLine(arg);
}
Console.ReadKey();
}
}
}
使用命令行参数运行它:
a "b" "\\x\\" "\x\"
在我得到的结果中:
Arguments:
a
b
\\x\
\x"
Command Line:
a
"b"
"\\x\\"
"\x\"
传递给方法Main()的args中缺少反斜杠和未删除的引号。除了手动解析Environment.CommandLine
?
答案 0 :(得分:19)
根据这个article by Jon Galloway,在命令行参数中使用反斜杠时可能会遇到奇怪的行为。
最引人注目的是它提到“大多数应用程序(包括.NET应用程序)使用CommandLineToArgvW来解码它们的命令行。它使用疯狂的转义规则来解释你所看到的行为。”
它解释了第一组反斜杠不需要转义,但是alpha(也许是数字?)字符后面的反斜杠需要转义,并且引号总是需要转义。
基于这些规则,我相信你得到你想要的参数:
a "b" "\\x\\\\" "\x\\"
确实,“哇哇”。
答案 1 :(得分:2)
我从另一个方面逃过了问题......
我没有获取已经解析的参数,而是按原样获取参数字符串,然后我使用自己的解析器:
static void Main(string[] args)
{
var param = ParseString(Environment.CommandLine);
...
}
// The following template implements the following notation:
// -key1 = some value -key2 = "some value even with '-' character " ...
private const string ParameterQuery = "\\-(?<key>\\w+)\\s*=\\s*(\"(?<value>[^\"]*)\"|(?<value>[^\\-]*))\\s*";
private static Dictionary<string, string> ParseString(string value)
{
var regex = new Regex(ParameterQuery);
return regex.Matches(value).Cast<Match>().ToDictionary(m => m.Groups["key"].Value, m => m.Groups["value"].Value);
}
此概念允许您键入不带转义前缀的引号。
答案 2 :(得分:1)
前几天我遇到了同样的问题,并且很难度过难关。在我的谷歌搜索中,我遇到this article regarding VB.NET(我的应用程序的语言)解决了问题,而不必根据参数更改任何其他代码。
在那篇文章中,他引用了为C#编写的original article。这是实际代码,您将其传递给Environment.CommandLine()
:
C#
class CommandLineTools
{
/// <summary>
/// C-like argument parser
/// </summary>
/// <param name="commandLine">Command line string with arguments. Use Environment.CommandLine</param>
/// <returns>The args[] array (argv)</returns>
public static string[] CreateArgs(string commandLine)
{
StringBuilder argsBuilder = new StringBuilder(commandLine);
bool inQuote = false;
// Convert the spaces to a newline sign so we can split at newline later on
// Only convert spaces which are outside the boundries of quoted text
for (int i = 0; i < argsBuilder.Length; i++)
{
if (argsBuilder[i].Equals('"'))
{
inQuote = !inQuote;
}
if (argsBuilder[i].Equals(' ') && !inQuote)
{
argsBuilder[i] = '\n';
}
}
// Split to args array
string[] args = argsBuilder.ToString().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
// Clean the '"' signs from the args as needed.
for (int i = 0; i < args.Length; i++)
{
args[i] = ClearQuotes(args[i]);
}
return args;
}
/// <summary>
/// Cleans quotes from the arguments.<br/>
/// All signle quotes (") will be removed.<br/>
/// Every pair of quotes ("") will transform to a single quote.<br/>
/// </summary>
/// <param name="stringWithQuotes">A string with quotes.</param>
/// <returns>The same string if its without quotes, or a clean string if its with quotes.</returns>
private static string ClearQuotes(string stringWithQuotes)
{
int quoteIndex;
if ((quoteIndex = stringWithQuotes.IndexOf('"')) == -1)
{
// String is without quotes..
return stringWithQuotes;
}
// Linear sb scan is faster than string assignemnt if quote count is 2 or more (=always)
StringBuilder sb = new StringBuilder(stringWithQuotes);
for (int i = quoteIndex; i < sb.Length; i++)
{
if (sb[i].Equals('"'))
{
// If we are not at the last index and the next one is '"', we need to jump one to preserve one
if (i != sb.Length - 1 && sb[i + 1].Equals('"'))
{
i++;
}
// We remove and then set index one backwards.
// This is because the remove itself is going to shift everything left by 1.
sb.Remove(i--, 1);
}
}
return sb.ToString();
}
}
VB.NET:
Imports System.Text
' Original version by Jonathan Levison (C#)'
' http://sleepingbits.com/2010/01/command-line-arguments-with-double-quotes-in-net/
' converted using http://www.developerfusion.com/tools/convert/csharp-to-vb/
' and then some manual effort to fix language discrepancies
Friend Class CommandLineHelper
''' <summary>
''' C-like argument parser
''' </summary>
''' <param name="commandLine">Command line string with arguments. Use Environment.CommandLine</param>
''' <returns>The args[] array (argv)</returns>
Public Shared Function CreateArgs(commandLine As String) As String()
Dim argsBuilder As New StringBuilder(commandLine)
Dim inQuote As Boolean = False
' Convert the spaces to a newline sign so we can split at newline later on
' Only convert spaces which are outside the boundries of quoted text
For i As Integer = 0 To argsBuilder.Length - 1
If argsBuilder(i).Equals(""""c) Then
inQuote = Not inQuote
End If
If argsBuilder(i).Equals(" "c) AndAlso Not inQuote Then
argsBuilder(i) = ControlChars.Lf
End If
Next
' Split to args array
Dim args As String() = argsBuilder.ToString().Split(New Char() {ControlChars.Lf}, StringSplitOptions.RemoveEmptyEntries)
' Clean the '"' signs from the args as needed.
For i As Integer = 0 To args.Length - 1
args(i) = ClearQuotes(args(i))
Next
Return args
End Function
''' <summary>
''' Cleans quotes from the arguments.<br/>
''' All signle quotes (") will be removed.<br/>
''' Every pair of quotes ("") will transform to a single quote.<br/>
''' </summary>
''' <param name="stringWithQuotes">A string with quotes.</param>
''' <returns>The same string if its without quotes, or a clean string if its with quotes.</returns>
Private Shared Function ClearQuotes(stringWithQuotes As String) As String
Dim quoteIndex As Integer = stringWithQuotes.IndexOf(""""c)
If quoteIndex = -1 Then Return stringWithQuotes
' Linear sb scan is faster than string assignemnt if quote count is 2 or more (=always)
Dim sb As New StringBuilder(stringWithQuotes)
Dim i As Integer = quoteIndex
Do While i < sb.Length
If sb(i).Equals(""""c) Then
' If we are not at the last index and the next one is '"', we need to jump one to preserve one
If i <> sb.Length - 1 AndAlso sb(i + 1).Equals(""""c) Then
i += 1
End If
' We remove and then set index one backwards.
' This is because the remove itself is going to shift everything left by 1.
sb.Remove(System.Math.Max(System.Threading.Interlocked.Decrement(i), i + 1), 1)
End If
i += 1
Loop
Return sb.ToString()
End Function
End Class
答案 3 :(得分:0)
经过多次实验,这对我有用。我试图创建一个发送到Windows命令行的命令。文件夹名称位于命令中的-graphical
选项之后,因为它可能包含空格,所以必须用双引号括起来。当我使用反斜杠创建引号时,它们在命令中以文字形式出现。所以这。 。 。
string q = @"" + (char) 34;
string strCmdText = string.Format(@"/C cleartool update -graphical {1}{0}{1}", this.txtViewFolder.Text, q);
System.Diagnostics.Process.Start("CMD.exe", strCmdText);
q
是一个只包含双引号字符的字符串。它之后是@
,使其成为verbatim string literal。
命令模板也是逐字字符串文字,string.Format方法用于将所有内容编译为strCmdText
。
答案 4 :(得分:0)
这适用于我,它可以正常使用问题中的示例。
/// <summary>
/// https://www.pinvoke.net/default.aspx/shell32/CommandLineToArgvW.html
/// </summary>
/// <param name="unsplitArgumentLine"></param>
/// <returns></returns>
static string[] SplitArgs(string unsplitArgumentLine)
{
int numberOfArgs;
IntPtr ptrToSplitArgs;
string[] splitArgs;
ptrToSplitArgs = CommandLineToArgvW(unsplitArgumentLine, out numberOfArgs);
// CommandLineToArgvW returns NULL upon failure.
if (ptrToSplitArgs == IntPtr.Zero)
throw new ArgumentException("Unable to split argument.", new Win32Exception());
// Make sure the memory ptrToSplitArgs to is freed, even upon failure.
try
{
splitArgs = new string[numberOfArgs];
// ptrToSplitArgs is an array of pointers to null terminated Unicode strings.
// Copy each of these strings into our split argument array.
for (int i = 0; i < numberOfArgs; i++)
splitArgs[i] = Marshal.PtrToStringUni(
Marshal.ReadIntPtr(ptrToSplitArgs, i * IntPtr.Size));
return splitArgs;
}
finally
{
// Free memory obtained by CommandLineToArgW.
LocalFree(ptrToSplitArgs);
}
}
[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine,
out int pNumArgs);
[DllImport("kernel32.dll")]
static extern IntPtr LocalFree(IntPtr hMem);
static string Reverse(string s)
{
char[] charArray = s.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
static string GetEscapedCommandLine()
{
StringBuilder sb = new StringBuilder();
bool gotQuote = false;
foreach (var c in Environment.CommandLine.Reverse())
{
if (c == '"')
gotQuote = true;
else if (gotQuote && c == '\\')
{
// double it
sb.Append('\\');
}
else
gotQuote = false;
sb.Append(c);
}
return Reverse(sb.ToString());
}
static void Main(string[] args)
{
// Crazy hack
args = SplitArgs(GetEscapedCommandLine()).Skip(1).ToArray();
}