繁体   English   中英

转义 C# 中的命令行参数

[英]Escape command line arguments in c#

精简版:

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

代码版本

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

根据这个答案,我创建了以下函数来转义单个参数,但我可能错过了一些东西。

private static string EscapeCommandLineArguments(string[] args)
{
    string arguments = "";
    foreach (string arg in args)
    {
        arguments += " \"" +
            arg.Replace ("\\", "\\\\").Replace("\"", "\\\"") +
            "\"";
    }
    return arguments;
}

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

然而,它比这更复杂!

我遇到了相关问题(编写前端 .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并且它实际上描述了这些情况的处理方式:反斜杠在 double 前面被视为转义引用。

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

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

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

就这样...

附注。 ……不是 等等,等等——还有更多! :)

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

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

我的回答类似于 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\\

你好,世界

“你好,世界”

\\“你好\\”

\\\\“你好\\\\\\”

\\“你好,世界

“\\\\“你好,世界”

\\“你好,世界\\

“\\\\“你好,世界\\\\”

你好,世界\\\\

“你好,世界\\\\\\\\”

我已经从每个人引用命令行参数错误的方式文章中移植了一个 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();
}

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

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

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

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

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

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 值内的所有引号。

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;
}

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

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

另一种方法

如果您正在传递一个复杂的对象,例如嵌套的 JSON,并且您可以控制接收命令行参数的系统,那么将命令行 arg/s 编码为 base64,然后从接收系统解码它们会容易得多。

请参阅此处:编码/解码字符串到/从 Base64

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

从此网址复制示例代码功能:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM