是否足以将参数包装在引号中并转义\
和"
?
我想使用ProcessInfo.Arguments将命令行参数string[] args
传递给另一个进程。
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = Application.ExecutablePath;
info.UseShellExecute = true;
info.Verb = "runas"; // Provides Run as Administrator
info.Arguments = EscapeCommandLineArguments(args);
Process.Start(info);
问题是我将参数作为数组获取,并且必须将它们合并为单个字符串。可以设计一个参数来欺骗我的程序。
my.exe "C:\Documents and Settings\MyPath \" --kill-all-humans \" except fry"
根据this answer我创建了以下函数来逃避单个参数,但我可能错过了一些东西。
private static string EscapeCommandLineArguments(string[] args)
{
string arguments = "";
foreach (string arg in args)
{
arguments += " \"" +
arg.Replace ("\\", "\\\\").Replace("\"", "\\\"") +
"\"";
}
return arguments;
}
这是否足够好或是否有任何框架功能?
答案 0 :(得分:62)
我遇到了相关问题(编写前端.exe会调用后端传递所有参数+一些额外的参数)所以我看了人们是怎么做的,遇到了你的问题。最初所有看起来都很好,因为你建议arg.Replace (@"\", @"\\").Replace(quote, @"\"+quote)
。
但是,当我使用参数c:\temp a\\b
进行调用时,会以c:\temp
和a\\b
传递,这会导致使用"c:\\temp" "a\\\\b"
调用后端 - 这是不正确的,因为会有两个参数c:\\temp
和a\\\\b
- 不是我们想要的!我们在逃脱中过于热心(窗户不是unix!)。
所以我详细阅读了http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx,它实际上描述了如何处理这些案例:反斜杠在双引号前面被视为只有的 。
在那里处理多个\
的方式有一个扭曲,解释可能会让人头晕一会儿。我会尝试在这里重新说出所说的unescape规则:说我们有一个 N \
的子字符串,然后是"
。取消时,我们用 int(N / 2) \
和iff N 替换该子字符串很奇怪,我们在最后添加"
。
这种解码的编码方式如下:对于一个参数,找到0或更多\
的每个子字符串,后跟"
,并将其替换为两倍多{{1} },然后是\
。我们可以这样做:
\"
这就是......
PS。 ...... 不。等等,等等 - 还有更多! :)
我们正确地进行了编码,但是因为你将所有参数括在双引号中(如果其中一些参数中有空格),则会有一些扭曲。存在边界问题 - 如果参数在s = Regex.Replace(arg, @"(\\*)" + "\"", @"$1$1\" + "\"");
结束,则在{1}}之后添加\
将破坏结束引用的含义。解析为"
和c:\one\ two
的示例c:\one\
将重新汇总到two
,我将(错误)理解为一个参数"c:\one\" "two"
(我试过了,我没有弥补)。所以我们还需要检查参数是否在c:\one" two
结束,如果是, double 结尾的反斜杠数,如下所示:
\
答案 1 :(得分:28)
我的回答类似于Nas Banov的答案,但我只是在必要时才想要双引号。
当你接近参数的字符限制时,我的代码会不必要地将双引号一直放在它周围,这很重要*。
/// <summary>
/// Encodes an argument for passing into a program
/// </summary>
/// <param name="original">The value that should be received by the program</param>
/// <returns>The value which needs to be passed to the program for the original value
/// to come through</returns>
public static string EncodeParameterArgument(string original)
{
if( string.IsNullOrEmpty(original))
return original;
string value = Regex.Replace(original, @"(\\*)" + "\"", @"$1\$0");
value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"");
return value;
}
// This is an EDIT
// Note that this version does the same but handles new lines in the arugments
public static string EncodeParameterArgumentMultiLine(string original)
{
if (string.IsNullOrEmpty(original))
return original;
string value = Regex.Replace(original, @"(\\*)" + "\"", @"$1\$0");
value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"", RegexOptions.Singleline);
return value;
}
要正确转义反斜杠和双引号,您只需替换多个反斜杠的任何实例,然后单个双引号 with:
string value = Regex.Replace(original, @"(\\*)" + "\"", @"\$1$0");
原始反斜杠 + 1和原始双引号的两倍。即'\'+ originalbackslashes + originalbackslashes +'“'。我使用$ 1 $ 0,因为$ 0有原始的反斜杠和原始的双引号所以它使得替换更好一读。
value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"");
这只能匹配包含空格的整行。
如果匹配则将双引号添加到开头和结尾。
如果参数末尾最初有反斜杠,则它们将不会被引用,现在它们需要的末尾有双引号。因此它们是重复的,引用它们全部,并防止无意中引用最终的双引号
它为第一部分进行最小匹配,以便最后一个。*?不会与最终的反斜杠
相匹配因此,这些输入产生以下输出
您好
<强>你好强>
\你好\ 12 \ 3 \
\你好\ 12 \ 3 \
你好世界
“你好世界”
\ “你好\”
<强> \\ “你好\\\”强>
\“hello \ world
“\\”hello \ world“
\“hello \\\ world \
“\\”hello \\\ world \\“
你好世界\\
“hello world \\\\”
答案 2 :(得分:6)
我也遇到了这个问题。我没有解析args,而是采用了完整的原始命令行并修剪了可执行文件。这具有在呼叫中保持空白的额外好处,即使不需要/使用它也是如此。它仍然必须追逐可执行文件中的转义,但这似乎比args更容易。
var commandLine = Environment.CommandLine;
var argumentsString = "";
if(args.Length > 0)
{
// Re-escaping args to be the exact same as they were passed is hard and misses whitespace.
// Use the original command line and trim off the executable to get the args.
var argIndex = -1;
if(commandLine[0] == '"')
{
//Double-quotes mean we need to dig to find the closing double-quote.
var backslashPending = false;
var secondDoublequoteIndex = -1;
for(var i = 1; i < commandLine.Length; i++)
{
if(backslashPending)
{
backslashPending = false;
continue;
}
if(commandLine[i] == '\\')
{
backslashPending = true;
continue;
}
if(commandLine[i] == '"')
{
secondDoublequoteIndex = i + 1;
break;
}
}
argIndex = secondDoublequoteIndex;
}
else
{
// No double-quotes, so args begin after first whitespace.
argIndex = commandLine.IndexOf(" ", System.StringComparison.Ordinal);
}
if(argIndex != -1)
{
argumentsString = commandLine.Substring(argIndex + 1);
}
}
Console.WriteLine("argumentsString: " + argumentsString);
答案 3 :(得分:4)
我已从Everyone quotes command line arguments the wrong way文章中移植了一个C ++函数。
它工作正常,但您应该注意cmd.exe
以不同方式解释命令行。如果(且仅当,就像文章的原始作者一样),您的命令行将由cmd.exe
解释,您还应该转义shell元字符。
/// <summary>
/// This routine appends the given argument to a command line such that
/// CommandLineToArgvW will return the argument string unchanged. Arguments
/// in a command line should be separated by spaces; this function does
/// not add these spaces.
/// </summary>
/// <param name="argument">Supplies the argument to encode.</param>
/// <param name="force">
/// Supplies an indication of whether we should quote the argument even if it
/// does not contain any characters that would ordinarily require quoting.
/// </param>
private static string EncodeParameterArgument(string argument, bool force = false)
{
if (argument == null) throw new ArgumentNullException(nameof(argument));
// Unless we're told otherwise, don't quote unless we actually
// need to do so --- hopefully avoid problems if programs won't
// parse quotes properly
if (force == false
&& argument.Length > 0
&& argument.IndexOfAny(" \t\n\v\"".ToCharArray()) == -1)
{
return argument;
}
var quoted = new StringBuilder();
quoted.Append('"');
var numberBackslashes = 0;
foreach (var chr in argument)
{
switch (chr)
{
case '\\':
numberBackslashes++;
continue;
case '"':
// Escape all backslashes and the following
// double quotation mark.
quoted.Append('\\', numberBackslashes*2 + 1);
quoted.Append(chr);
break;
default:
// Backslashes aren't special here.
quoted.Append('\\', numberBackslashes);
quoted.Append(chr);
break;
}
numberBackslashes = 0;
}
// Escape all backslashes, but let the terminating
// double quotation mark we add below be interpreted
// as a metacharacter.
quoted.Append('\\', numberBackslashes*2);
quoted.Append('"');
return quoted.ToString();
}
答案 4 :(得分:3)
我在GitHub上发布了一个小项目,用于处理命令行编码/转义的大多数问题:
https://github.com/ericpopivker/Command-Line-Encoder
有一个CommandLineEncoder.Utils.cs类,以及用于验证编码/解码功能的单元测试。
答案 5 :(得分:2)
我给你写了一个小样本,向你展示如何在命令行中使用转义字符。
public static string BuildCommandLineArgs(List<string> argsList)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (string arg in argsList)
{
sb.Append("\"\"" + arg.Replace("\"", @"\" + "\"") + "\"\" ");
}
if (sb.Length > 0)
{
sb = sb.Remove(sb.Length - 1, 1);
}
return sb.ToString();
}
这是一种测试方法:
List<string> myArgs = new List<string>();
myArgs.Add("test\"123"); // test"123
myArgs.Add("test\"\"123\"\"234"); // test""123""234
myArgs.Add("test123\"\"\"234"); // test123"""234
string cmargs = BuildCommandLineArgs(myArgs);
// result: ""test\"123"" ""test\"\"123\"\"234"" ""test123\"\"\"234""
// when you pass this result to your app, you will get this args list:
// test"123
// test""123""234
// test123"""234
重点是用双引号(“”arg“”)包装每个arg,并用转义引号(test \“123)替换arg值中的所有引号。
答案 6 :(得分:1)
static string BuildCommandLineFromArgs(params string[] args)
{
if (args == null)
return null;
string result = "";
if (Environment.OSVersion.Platform == PlatformID.Unix
||
Environment.OSVersion.Platform == PlatformID.MacOSX)
{
foreach (string arg in args)
{
result += (result.Length > 0 ? " " : "")
+ arg
.Replace(@" ", @"\ ")
.Replace("\t", "\\\t")
.Replace(@"\", @"\\")
.Replace(@"""", @"\""")
.Replace(@"<", @"\<")
.Replace(@">", @"\>")
.Replace(@"|", @"\|")
.Replace(@"@", @"\@")
.Replace(@"&", @"\&");
}
}
else //Windows family
{
bool enclosedInApo, wasApo;
string subResult;
foreach (string arg in args)
{
enclosedInApo = arg.LastIndexOfAny(
new char[] { ' ', '\t', '|', '@', '^', '<', '>', '&'}) >= 0;
wasApo = enclosedInApo;
subResult = "";
for (int i = arg.Length - 1; i >= 0; i--)
{
switch (arg[i])
{
case '"':
subResult = @"\""" + subResult;
wasApo = true;
break;
case '\\':
subResult = (wasApo ? @"\\" : @"\") + subResult;
break;
default:
subResult = arg[i] + subResult;
wasApo = false;
break;
}
}
result += (result.Length > 0 ? " " : "")
+ (enclosedInApo ? "\"" + subResult + "\"" : subResult);
}
}
return result;
}
答案 7 :(得分:0)
添加参数很好,但不会逃避。在转义序列应该去的方法中添加注释。
public static string ApplicationArguments()
{
List<string> args = Environment.GetCommandLineArgs().ToList();
args.RemoveAt(0); // remove executable
StringBuilder sb = new StringBuilder();
foreach (string s in args)
{
// todo: add escape double quotes here
sb.Append(string.Format("\"{0}\" ", s)); // wrap all args in quotes
}
return sb.ToString().Trim();
}
答案 8 :(得分:0)
如果您要传递复杂的对象(例如嵌套的JSON),并且可以控制接收命令行参数的系统,那么将命令行arg / s编码为base64然后从接收方对其进行解码会容易得多。系统。
参见此处:Encode/Decode String to/from Base64
用例:我需要传递一个JSON对象,该对象在其中一个属性中包含一个XML字符串,该属性过于复杂以至于无法转义。这样就解决了。
答案 9 :(得分:0)
从此网址复制示例代码功能:
http://csharptest.net/529/how-to-correctly-escape-command-line-arguments-in-c/index.html
例如,您可以获取命令行来执行:
String cmdLine = EscapeArguments(Environment.GetCommandLineArgs().Skip(1).ToArray());
Skip(1)
跳过可执行文件名称。