Подтвердить что ты не робот

Обратная косая черта и цитата в аргументах командной строки

Является ли следующее поведение некоторой функцией или ошибкой в ​​С#.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(), отсутствуют обратные слэши и неотключенная цитата. Кто-нибудь знает о правильном обходном пути, кроме ручного разбора CommandLine?

4b9b3361

Ответ 1

В соответствии с этой статьей Большинство приложений (в том числе .Net-приложений) используют CommandLineToArgvW для декодирования своих командных строк. В нем используются сумасшедшие правила экранирования, которые объясняют поведение, которое вы видите."

В нем объясняется, что первый набор обратных косых черт не требует экранирования, но обратные косые черты, следующие после альфа (возможно, числовые??), требуют экранирования, и эти кавычки всегда должны быть экранированы.

Основываясь на этих правилах, я считаю, что вы получите аргументы, которые вы хотите, чтобы передать их следующим образом:

a "b" "\\x\\\\" "\x\\"

"Бешено" действительно.

Ответ 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);
} 

эта концепция позволяет вам вводить кавычки без префикса escape

Ответ 3

После долгих экспериментов это сработало для меня. Я пытаюсь создать команду для отправки в командной строке 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 - это строка, содержащая только символ двойной кавычки. Ему предшествует @ сделать его дословным строковым литералом.

Шаблон команды также является строковым литералом, и метод string.Format используется для компиляции всего в strCmdText.

Ответ 4

Я наткнулся на эту же проблему на днях и с трудом прошел через нее. В моем Googling я наткнулся на эту статью относительно VB.NET (язык моего приложения), которая решила проблему, не меняя ни одной из мой другой код, основанный на аргументах.

В этой статье он ссылается на оригинальную статью, которая была написана для С#. Здесь фактический код, вы передаете его Environment.CommandLine():

С#

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