C#中的转义命令行参数


75

简洁版本:

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

代码版本

我想string[] args使用ProcessInfo.Arguments将命令行参数传递给另一个进程。

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

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


5
您是否尝试过原样?我认为,如果将其传递给您,则可以将其传递给另一个命令。如果遇到任何错误,可以考虑转义。
2011年

2
@Sanjeevakumar是的,例如:"C:\Documents and Settings\MyPath \" --kill-all-humans \" except fry"因为我正在进行特权呼叫,所以这不是一件好事。
hultqvist 2011年

1
@Sanjeevakumar Main(string [] args)是未转义的字符串数组,因此,如果我运行my.exe "test\"test"arg [0],将是test"test
hultqvist 2011年

1.只是根据您的第一条评论逃避,似乎逃避不是您想要的。2.什么是未转义的字符串?当您收到类似的字符串时abc"defabc"def为什么要立即对其进行转义?如果您要添加“ abc” +“””” +“ def”之类的内容,那么这很有意义。观察""""逃逸"
Sanjeevakumar Hiremath 2011年

是的abc"def,因为输入是正确的,但是如果我要将其传递给另一个进程,则必须在将其添加到单个字符串参数之前对其进行转义。请参阅更新的问题以进行澄清。
hultqvist 2011年

Answers:


68

比这还复杂!

我遇到了相关的问题(编写前端.exe,它将调用传递所有参数的后端+一些额外的参数),因此我查看了人们是如何做到的,遇到了您的问题。最初,按照您的建议,一切似乎都很好arg.Replace (@"\", @"\\").Replace(quote, @"\"+quote)

但是,当我使用arguments进行调用时,它将c:\temp a\\b作为c:\temp和传递a\\b,这导致使用调用后端"c:\\temp" "a\\\\b"-这是不正确的,因为会有两个参数, c:\\tempa\\\\b-不是我们想要的!我们一直在逃逸(窗口不是unix!)。

因此,我详细阅读了http://msdn.microsoft.com/zh-cn/library/system.environment.getcommandlineargs.aspx,它实际上描述了如何处理这些情况:反斜杠在double前面被视为转义引用。

在这里如何\处理多个有一个扭曲,说明可能会使您头昏眼花一会儿。在这里,我将尝试重新表述所说的不转义规则:说我们有一个N 的子字符串\,然后是"。进行转义时,我们用int(N / 2) 替换该子字符串,\并且如果N为奇数,则"在末尾添加。

这样的解码的编码将是这样的:对于一个参数,找到0或更大的每个子串,\然后跟着,"并用两次多次替换它\,然后是\"。我们可以这样做:

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

就这样...

PS。...不是。等等,等等-还有更多!:)

我们正确地进行了编码,但是有一个扭曲,因为您将所有参数都括在双引号中(以防某些参数中有空格)。有一个边界问题-万一参数以结尾\"在它后面添加会破坏右引号的含义。c:\one\ two解析为示例c:\one\two然后将其重新组合为"c:\one\" "two"我(误)理解为一个参数c:\one" two(我尝试过,但我没有做)。因此,我们还需要检查参数是否以结尾结尾\,如果是,则将结尾的反斜杠数量加倍,如下所示:

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

6
+1用于解释这种精神错乱。但是上述匹配表达式中的*和括号中的和是否不应该+包含括号中?否则,$1替换将只能是单个反斜杠。
bobince 2011年

实际上,我认为这两个替代项可以合并为:"\""+Regex.Replace(s, "(\\\\*)(\\\\$|\")", "$1$1\\$2")+"\""。但是我的大脑现在开始下沉,如果可以检查正确性,我会非常感激:-)
bobince 2011年


1
感谢您的回答!你能加TL吗?DR静态方法可以处理所有问题?我真的很喜欢您的答案,但是每次需要信息时,我都必须阅读并理解它(因为我太愚蠢,无法完全记住它)...
vojta

@vojta-抱歉,已经过去五年了,我不记得详细信息了。通过重新阅读我写的内容,我想只需要调用这两行。但是您现在可能对此情况有了更好的了解,为什么不编辑答案,而后代则添加TL-DNR?
Nas Banov

31

我的答案与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和原始双引号加倍。我使用$ 1 $ 0,因为$ 0具有原始的反斜杠和原始的双引号,因此它使替换成为更好的阅读方式。

value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"");

这只能匹配包含空格的整行。

如果匹配,则添加双引号在开头和结尾。

如果在参数的末尾最初有反斜杠,则不会再加引号,而现在在末尾必须加双引号。因此它们是重复的,用引号将所有引号引起来,并防止无意中引用最后的引号双引号

它对第一部分进行最小匹配,以便最后一个。*?不参加决赛反斜杠

输出量

因此这些输入产生以下输出

你好

你好

\ hello \ 12 \ 3 \

\ hello \ 12 \ 3 \

你好,世界

“你好,世界”

\“你好\”

\\“你好\\\”

\“你好,世界

“\\“你好,世界”

\“你好,世界\

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

你好,世界\\

“你好,世界\\\\”


1
一个较小的修正:当original为空时,您需要返回一对双引号""而不是一个空字符串,因此命令行将知道那里有一个参数。除此之外,这非常完美!
Joey Adams

必须有一个错误...输入:<a>\n <b/>\n</a>。输出:<a>\n <b/>\n</a>。看起来外部qoutes丢失了!难道我做错了什么?(\n当然,这意味着换行符,所以注释不是真的对换行符友好的)
2016年

我什至从未想到要在其中添加新行作为参数。不能粘贴代码在这里似乎..我会改变我的答案,包括原始和一个把手新线
马特Vukomanovic

7

我从错误的方式文章从Everyone quotes命令行参数移植了C ++函数。

它工作正常,但您应注意,cmd.exe命令行的解释有所不同。如果(并且仅当,如所述文章的原始作者所述)您的命令行将被解释时,cmd.exe您还应该转义外壳元字符。

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

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

1
将您的代码转换为C函数:LPWSTR GetArgStrFromCommandLine(LPWSTR c) {if (*c++ != L'"') c = wcspbrk(--c, L" \t\r\n\v\f"); else while (*c && *c++ != L'"') if (*c == L'\\') ++c; return c;}
7vujy0f0hy


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


您的示例可用,但是@“ \ test”不起作用,并且@“ test \”与Win32Exception断开。当我将路径作为参数传递时,后者在我的工作中很常见。
hultqvist

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

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

1
恐怕您的代码仅将参数括在引号中,但不会转义。如果我只my.exe "arg1\" \"arg2"给出一个参数,arg1" "arg2您的代码将生成两个参数,arg1并且arg2
hultqvist 2011年

好的,我还没有对此进行测试。我想有一个理由arg1" "arg2尽管我无法想象为什么。您的权利无论如何我都应该逃脱,我将观看此主题以了解谁为此提出了最佳机制。
Chuck Savage

我可以想到两个。1:出于恶意的人试图诱使您的程序执行危险的命令。2:传递论点John "The Boss" Smith
hultqvist 2011年

0

另一种方法

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

参见此处:对Base64进行编码/解码字符串

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.