在 C# 中转义命令行参数

Posted

技术标签:

【中文标题】在 C# 中转义命令行参数【英文标题】:Escape command line arguments in c# 【发布时间】:2011-07-27 11:45:57 【问题描述】:

短版:

将参数用引号括起来并转义\" 是否足够?

代码版本

我想使用 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;

这足够好还是有任何框架功能?

【问题讨论】:

您是否尝试按原样通过?我认为如果将其传递给您,则可以将其传递给另一个命令。如果你遇到任何错误,那么你可以考虑转义。 @Sanjeevakumar 是的,例如:"C:\Documents and Settings\MyPath \" --kill-all-humans \" except fry" 不会是一件好事,因为我正在拨打特权电话。 @Sanjeevakumar Main(string[] args) 是一个非转义字符串数组,所以如果我运行my.exe "test\"test" arg[0] 将是test"test 1.您是否只想根据您的第一条评论进行转义,看起来转义不是您想要做的。 2.什么是未转义字符串?当你得到一个像abc"def 这样的字符串时,它是abc"def 为什么你现在想逃避它?如果您要添加诸如“abc”+“”“”+“def”之类的内容,这是有道理的。观察"""" 正在转义" 是的abc"def 给定输入是正确的,但是如果我要将它传递给另一个进程,我必须在将其添加到单个字符串参数之前对其进行转义。请参阅更新后的问题进行说明。 【参考方案1】:

从此网址复制示例代码函数:

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) 跳过可执行文件名。

【讨论】:

【参考方案2】:

我的回答与 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;

解释

要正确转义反斜杠双引号,您只需替换多个反斜杠的任何实例,后跟一个双引号 与:

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\

你好世界

“你好世界”

\"你好\"

\\"你好\\\\"

\"你好\世界

"\\"你好\世界"

\"你好\\\世界\

"\\"你好\\\世界\\"

你好世界\\

“你好世界\\\\”

【讨论】:

一个小修复:当 original 为空时,您需要返回一对双引号 "" 而不是空字符串,因此命令行会知道有一个参数。除此之外,这非常有效! 肯定有bug... 输入:&lt;a&gt;\n &lt;b/&gt;\n&lt;/a&gt;。输出:&lt;a&gt;\n &lt;b/&gt;\n&lt;/a&gt;。看起来外部 qoutes 不见了!难道我做错了什么? (\n 表示换行,当然,SO cmets 并不是真正的换行友好) 我从没想过要在其中加入新行进行论证。似乎无法在此处粘贴代码。我将更改答案以包括原始答案和处理新行的答案【参考方案3】:

另一种方法

如果您传递的是嵌套 JSON 等复杂对象,并且您可以控制接收命令行参数的系统,则只需将命令行 arg/s 编码为 base64,然后从接收中解码它们就容易得多系统。

请看这里:Encode/Decode String to/from Base64

用例:我需要传递一个 JSON 对象,该对象在其中一个属性中包含一个 XML 字符串,而该属性的转义过于复杂。这样就解决了。

【讨论】:

【参考方案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();

【讨论】:

【参考方案5】:

我也遇到了这个问题。我没有解析 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);

【讨论】:

把你的代码变成了一个 C 函数:LPWSTR GetArgStrFromCommandLine(LPWSTR c) if (*c++ != L'"') c = wcspbrk(--c, L" \t\r\n\v\f"); else while (*c &amp;&amp; *c++ != L'"') if (*c == L'\\') ++c; return c;【参考方案6】:

但比这更复杂!

我遇到了相关问题(编写前端 .exe,它将调用后端并传递所有参数 + 一些额外的参数),所以我看看人们是如何做到的,遇到了你的问题。最初看起来一切都很好,正如你建议的那样arg.Replace (@"\", @"\\").Replace(quote, @"\"+quote)

但是,当我使用参数c:\temp a\\b 调用时,它会以c:\tempa\\b 的形式传递,这会导致使用"c:\\temp" "a\\\\b" 调用后端 - 这是不正确的,因为会有两个参数c:\\tempa\\\\b - 不是我们想要的!我们在转义方面过分热心(windows 不是 unix!)。

所以我详细阅读了http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx,它实际上描述了这些情况是如何处理的:反斜杠在双引号前被视为only转义。

那里如何处理多个\ 有一个转折点,这个解释可能会让一个人头晕目眩。我将尝试在此处重新表述上述 unescape 规则:假设我们有一个 N \ 的子字符串,然后是 "。取消转义时,我们将那个子字符串替换为 int(N/2) \ 并且如果 N 是奇数,我们在末尾添加"

这种解码的编码是这样的:对于一个参数,找到 0 个或多个 \ 的每个子字符串,然后是 ",并将其替换为两倍的 \,然后是 @ 987654338@。我们可以这样做:

s = Regex.Replace(arg, @"(\\*)" + "\"", @"$1$1\" + "\"");

就是这样……

附言。 ... 不是。等等,等等——还有更多! :)

我们正确地进行了编码,但有一个转折,因为您将所有参数括在双引号中(以防其中一些有空格)。存在边界问题 - 如果参数以 \ 结尾,则在其后添加 " 将破坏关闭引号的含义。示例c:\one\ two 解析为c:\one\two 然后将被重新组装为"c:\one\" "two",这将我(错误)理解为一个参数c:\one" two(我试过了,我没有编它)。因此,我们还需要检查参数是否以 \ 结尾,如果是,则 double 末尾的反斜杠数,如下所示:

s = "\"" + Regex.Replace(s, @"(\\+)$", @"$1$1") + "\"";

【讨论】:

+1 用于解释这种精神错乱。但是,*+ 不应该是 inside 上述匹配表达式中的分组括号吗?否则,$1 替换将永远只是一个反斜杠。 其实我认为这两个替换可以合并成:"\""+Regex.Replace(s, "(\\\\*)(\\\\$|\")", "$1$1\\$2")+"\""。但是,如果您能检查正确性,我的大脑现在开始下沉,非常感谢:-) 仅供参考:blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/… 感谢您的回答!你能加个TL吗?处理所有事情的 DR 静态方法?我真的很喜欢你的回答,但每次需要信息时我都必须阅读并理解它(因为我太笨了,无法完全记住它)...... @vojta - 我很抱歉,但已经五年了,我不记得细节了。通过重新阅读我写的内容,我想只需要调用这两行。但是您现在可能对案例有了更好的了解,为什么不编辑答案并为后代添加 TL-DNR ?【参考方案7】:

我在 GitHub 上发布了一个小项目,用于处理大多数命令行编码/转义问题:

https://github.com/ericpopivker/Command-Line-Encoder

有一个CommandLineEncoder.Utils.cs 类,以及验证编码/解码功能的单元测试。

【讨论】:

【参考方案8】:
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;

【讨论】:

【参考方案9】:

在添加参数方面做得很好,但不会逃避。在转义序列应该去的方法中添加了注释。

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();

【讨论】:

恐怕您的代码只将参数括在引号中,但它不会进行任何转义。如果我运行my.exe "arg1\" \"arg2" 给出一个参数arg1" "arg2 你的代码将生成两个参数arg1arg2 好的,我没有测试过。我想有理由做arg1" "arg2 虽然我无法想象为什么。你的权利无论如何我都应该在那里逃跑,我会看这个帖子,看看谁想出了最好的机制。 我能想到两个。 1:有恶意的人试图欺骗你的程序执行危险的命令。 2:传递参数John "The Boss" Smith【参考方案10】:

我给你写了一个小例子,向你展示如何在命令行中使用转义字符。

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"" ) 括起来,并将 arg 值内的所有引号替换为转义引号 ( test\"123 )。

【讨论】:

您的示例有效,但 @"\test" 无效,@"test\" 因 Win32Exception 而中断。在将路径作为参数传递时,后者在我的工作中很常见。

以上是关于在 C# 中转义命令行参数的主要内容,如果未能解决你的问题,请参考以下文章

C#中的命令行参数

在 Windows 中转义 curl 命令

如何在 C# 中解析命令行参数 [重复]

在 C# 中解析命令行参数/选项

如何在 C# 中访问 Main 之外的命令行参数

如何在 C# 中读取另一个进程的命令行参数?