Разбить строку, содержащую параметры командной строки, на строку [] в C # - PullRequest
80 голосов
/ 18 ноября 2008

У меня есть одна строка, содержащая параметры командной строки для передачи другому исполняемому файлу, и мне нужно извлечь строку [], содержащую отдельные параметры, так же, как C #, если бы команды были указаны в команде -линия. Строка [] будет использоваться при выполнении другой точки входа сборок с помощью отражения.

Есть ли стандартная функция для этого? Или есть предпочтительный метод (регулярное выражение?) Для правильного разделения параметров? Он должен обрабатывать строки с разделителями "" ", которые могут содержать пробелы правильно, поэтому я не могу просто разбить на" ".

Пример строки:

string parameterString = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo";

Пример результата:

string[] parameterArray = new string[] { 
  @"/src:C:\tmp\Some Folder\Sub Folder",
  @"/users:abcdefg@hijkl.com",
  @"tasks:SomeTask,Some Other Task",
  @"-someParam",
  @"foo"
};

Мне не нужна библиотека для разбора командной строки, просто способ получить строку [], которая должна быть сгенерирована.

Обновление : мне пришлось изменить ожидаемый результат, чтобы он соответствовал тому, что фактически генерируется C # (убрал лишние "в разделенных строках)

Ответы [ 21 ]

1 голос
/ 01 ноября 2013

Использование:

public static string[] SplitArguments(string args) {
    char[] parmChars = args.ToCharArray();
    bool inSingleQuote = false;
    bool inDoubleQuote = false;
    bool escaped = false;
    bool lastSplitted = false;
    bool justSplitted = false;
    bool lastQuoted = false;
    bool justQuoted = false;

    int i, j;

    for(i=0, j=0; i<parmChars.Length; i++, j++) {
        parmChars[j] = parmChars[i];

        if(!escaped) {
            if(parmChars[i] == '^') {
                escaped = true;
                j--;
            } else if(parmChars[i] == '"' && !inSingleQuote) {
                inDoubleQuote = !inDoubleQuote;
                parmChars[j] = '\n';
                justSplitted = true;
                justQuoted = true;
            } else if(parmChars[i] == '\'' && !inDoubleQuote) {
                inSingleQuote = !inSingleQuote;
                parmChars[j] = '\n';
                justSplitted = true;
                justQuoted = true;
            } else if(!inSingleQuote && !inDoubleQuote && parmChars[i] == ' ') {
                parmChars[j] = '\n';
                justSplitted = true;
            }

            if(justSplitted && lastSplitted && (!lastQuoted || !justQuoted))
                j--;

            lastSplitted = justSplitted;
            justSplitted = false;

            lastQuoted = justQuoted;
            justQuoted = false;
        } else {
            escaped = false;
        }
    }

    if(lastQuoted)
        j--;

    return (new string(parmChars, 0, j)).Split(new[] { '\n' });
}

На основании ответа Vapor в переулке , этот также поддерживает ^ escapes.

Примеры:

  • это тест
    • это
    • есть
    • а
    • тест
  • это "это" тест
    • это
    • является
    • тест
  • этот ^ "это ^" тест
    • это
    • "есть
    • а "
    • тест
  • это "" "тест ^^"
    • это
    • это ^ тест

Он также поддерживает несколько пробелов (разбивает аргументы только один раз на блок пробелов).

1 голос
/ 14 ноября 2018

О черт. Это все ... Эхх. Но это законно официально. Из Microsoft в C # для .NET Core, может быть, только для Windows, может быть кроссплатформенным, но MIT лицензирован.

Выберите лакомые кусочки, объявления методов и заметные комментарии;

internal static unsafe string[] InternalCreateCommandLine(bool includeArg0)
private static unsafe int SegmentCommandLine(char * pCmdLine, string[] argArray, bool includeArg0)
private static unsafe int ScanArgument0(ref char* psrc, char[] arg)
private static unsafe int ScanArgument(ref char* psrc, ref bool inquote, char[] arg)

-

// First, parse the program name (argv[0]). Argv[0] is parsed under special rules. Anything up to 
// the first whitespace outside a quoted subtring is accepted. Backslashes are treated as normal 
// characters.

-

// Rules: 2N backslashes + " ==> N backslashes and begin/end quote
//      2N+1 backslashes + " ==> N backslashes + literal "
//         N backslashes     ==> N backslashes

Это код, портированный на .NET Core из .NET Framework, который, как я полагаю, является либо библиотекой MSVC C, либо CommandLineToArgvW.

Вот моя нерешительная попытка обработать некоторые из махинаций с помощью регулярных выражений и игнорировать нулевой бит аргумента. Это немного волшебство.

private static readonly Regex RxWinArgs
  = new Regex("([^\\s\"]+\"|((?<=\\s|^)(?!\"\"(?!\"))\")+)(\"\"|.*?)*\"[^\\s\"]*|[^\\s]+",
    RegexOptions.Compiled
    | RegexOptions.Singleline
    | RegexOptions.ExplicitCapture
    | RegexOptions.CultureInvariant);

internal static IEnumerable<string> ParseArgumentsWindows(string args) {
  var match = RxWinArgs.Match(args);

  while (match.Success) {
    yield return match.Value;
    match = match.NextMatch();
  }
}

Протестировал его на сумасшедшем сгенерированном выходе. Его вывод соответствует значительному проценту от того, что набрали обезьяны и набрали CommandLineToArgvW.

0 голосов
/ 28 декабря 2017

Я не думаю, что для приложений C # есть одинарные или ^ кавычки. Следующая функция работает нормально для меня:

public static IEnumerable<String> SplitArguments(string commandLine)
{
    Char quoteChar = '"';
    Char escapeChar = '\\';
    Boolean insideQuote = false;
    Boolean insideEscape = false;

    StringBuilder currentArg = new StringBuilder();

    // needed to keep "" as argument but drop whitespaces between arguments
    Int32 currentArgCharCount = 0;                  

    for (Int32 i = 0; i < commandLine.Length; i++)
    {
        Char c = commandLine[i];
        if (c == quoteChar)
        {
            currentArgCharCount++;

            if (insideEscape)
            {
                currentArg.Append(c);       // found \" -> add " to arg
                insideEscape = false;
            }
            else if (insideQuote)
            {
                insideQuote = false;        // quote ended
            }
            else
            {
                insideQuote = true;         // quote started
            }
        }
        else if (c == escapeChar)
        {
            currentArgCharCount++;

            if (insideEscape)   // found \\ -> add \\ (only \" will be ")
                currentArg.Append(escapeChar + escapeChar);       

            insideEscape = !insideEscape;
        }
        else if (Char.IsWhiteSpace(c))
        {
            if (insideQuote)
            {
                currentArgCharCount++;
                currentArg.Append(c);       // append whitespace inside quote
            }
            else
            {
                if (currentArgCharCount > 0)
                    yield return currentArg.ToString();

                currentArgCharCount = 0;
                currentArg.Clear();
            }
        }
        else
        {
            currentArgCharCount++;
            if (insideEscape)
            {
                // found non-escaping backslash -> add \ (only \" will be ")
                currentArg.Append(escapeChar);                       
                currentArgCharCount = 0;
                insideEscape = false;
            }
            currentArg.Append(c);
        }
    }

    if (currentArgCharCount > 0)
        yield return currentArg.ToString();
}
0 голосов
/ 08 апреля 2017

Вот один вкладыш, который выполняет работу (см. Одну строку, которая выполняет всю работу внутри метода BurstCmdLineArgs (...)).

Не то, что я бы назвал наиболее читаемой строкой кода, но вы можете выделить ее для удобства чтения. Это просто нарочно и не работает хорошо для всех случаев аргументов (например, аргументы имени файла, которые содержат разделитель символов разделенной строки в них).

Это решение хорошо работает в моих решениях, которые его используют. Как я уже сказал, он выполняет свою работу без крысиного гнезда кода для обработки каждого возможного формата аргумента n-факториала.

using System;
using System.Collections.Generic;
using System.Linq;

namespace CmdArgProcessor
{
    class Program
    {
        static void Main(string[] args)
        {
            // test switches and switches with values
            // -test1 1 -test2 2 -test3 -test4 -test5 5

            string dummyString = string.Empty;

            var argDict = BurstCmdLineArgs(args);

            Console.WriteLine("Value for switch = -test1: {0}", argDict["test1"]);
            Console.WriteLine("Value for switch = -test2: {0}", argDict["test2"]);
            Console.WriteLine("Switch -test3 is present? {0}", argDict.TryGetValue("test3", out dummyString));
            Console.WriteLine("Switch -test4 is present? {0}", argDict.TryGetValue("test4", out dummyString));
            Console.WriteLine("Value for switch = -test5: {0}", argDict["test5"]);

            // Console output:
            //
            // Value for switch = -test1: 1
            // Value for switch = -test2: 2
            // Switch -test3 is present? True
            // Switch -test4 is present? True
            // Value for switch = -test5: 5
        }

        public static Dictionary<string, string> BurstCmdLineArgs(string[] args)
        {
            var argDict = new Dictionary<string, string>();

            // Flatten the args in to a single string separated by a space.
            // Then split the args on the dash delimiter of a cmd line "switch".
            // E.g. -mySwitch myValue
            //  or -JustMySwitch (no value)
            //  where: all values must follow a switch.
            // Then loop through each string returned by the split operation.
            // If the string can be split again by a space character,
            // then the second string is a value to be paired with a switch,
            // otherwise, only the switch is added as a key with an empty string as the value.
            // Use dictionary indexer to retrieve values for cmd line switches.
            // Use Dictionary::ContainsKey(...) where only a switch is recorded as the key.
            string.Join(" ", args).Split('-').ToList().ForEach(s => argDict.Add(s.Split()[0], (s.Split().Count() > 1 ? s.Split()[1] : "")));

            return argDict;
        }
    }
}
0 голосов
/ 25 июля 2015

Попробуйте этот код:

    string[] str_para_linha_comando(string str, out int argumentos)
    {
        string[] linhaComando = new string[32];
        bool entre_aspas = false;
        int posicao_ponteiro = 0;
        int argc = 0;
        int inicio = 0;
        int fim = 0;
        string sub;

        for(int i = 0; i < str.Length;)
        {
            if (entre_aspas)
            {
                // Está entre aspas
                sub = str.Substring(inicio+1, fim - (inicio+1));
                linhaComando[argc - 1] = sub;

                posicao_ponteiro += ((fim - posicao_ponteiro)+1);
                entre_aspas = false;
                i = posicao_ponteiro;
            }
            else
            {
            tratar_aspas:
                if (str.ElementAt(i) == '\"')
                {
                    inicio = i;
                    fim = str.IndexOf('\"', inicio + 1);
                    entre_aspas = true;
                    argc++;
                }
                else
                {
                    // Se não for aspas, então ler até achar o primeiro espaço em branco
                    if (str.ElementAt(i) == ' ')
                    {
                        if (str.ElementAt(i + 1) == '\"')
                        {
                            i++;
                            goto tratar_aspas;
                        }

                        // Pular os espaços em branco adiconais
                        while(str.ElementAt(i) == ' ') i++;

                        argc++;
                        inicio = i;
                        fim = str.IndexOf(' ', inicio);
                        if (fim == -1) fim = str.Length;
                        sub = str.Substring(inicio, fim - inicio);
                        linhaComando[argc - 1] = sub;
                        posicao_ponteiro += (fim - posicao_ponteiro);

                        i = posicao_ponteiro;
                        if (posicao_ponteiro == str.Length) break;
                    }
                    else
                    {
                        argc++;
                        inicio = i;
                        fim = str.IndexOf(' ', inicio);
                        if (fim == -1) fim = str.Length;

                        sub = str.Substring(inicio, fim - inicio);
                        linhaComando[argc - 1] = sub;
                        posicao_ponteiro += fim - posicao_ponteiro;
                        i = posicao_ponteiro;
                        if (posicao_ponteiro == str.Length) break;
                    }
                }
            }
        }

        argumentos = argc;

        return linhaComando;
    }

Написано на португальском.

0 голосов
/ 26 апреля 2012

Вы можете взглянуть на код, который я отправил вчера:

[C #] Строки пути и аргументов

Он разбивает имя файла + аргументы на строку []. Короткие пути, переменные среды и отсутствующие расширения файлов обрабатываются.

(Первоначально это было для UninstallString в реестре.)

0 голосов
/ 18 ноября 2008

Я не уверен, что понял вас, но проблема в том, что символ, используемый в качестве разделителя, также находится внутри текста? (За исключением того, что он экранируется с двойным "?)

Если это так, я бы создал цикл for и заменил бы все экземпляры, где <"> присутствует, на <|> (или другой" безопасный "символ, но убедитесь, что он заменяет только <">, а не < "">

После итерации строки я бы сделал, как раньше, разделил строку, но теперь по символу <|>.

0 голосов
/ 18 ноября 2008

В настоящее время это код, который у меня есть:

    private String[] SplitCommandLineArgument(String argumentString)
    {
        StringBuilder translatedArguments = new StringBuilder(argumentString);
        bool escaped = false;
        for (int i = 0; i < translatedArguments.Length; i++)
        {
            if (translatedArguments[i] == '"')
            {
                escaped = !escaped;
            }
            if (translatedArguments[i] == ' ' && !escaped)
            {
                translatedArguments[i] = '\n';
            }
        }

        string[] toReturn = translatedArguments.ToString().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
        for(int i = 0; i < toReturn.Length; i++)
        {
            toReturn[i] = RemoveMatchingQuotes(toReturn[i]);
        }
        return toReturn;
    }

    public static string RemoveMatchingQuotes(string stringToTrim)
    {
        int firstQuoteIndex = stringToTrim.IndexOf('"');
        int lastQuoteIndex = stringToTrim.LastIndexOf('"');
        while (firstQuoteIndex != lastQuoteIndex)
        {
            stringToTrim = stringToTrim.Remove(firstQuoteIndex, 1);
            stringToTrim = stringToTrim.Remove(lastQuoteIndex - 1, 1); //-1 because we've shifted the indicies left by one
            firstQuoteIndex = stringToTrim.IndexOf('"');
            lastQuoteIndex = stringToTrim.LastIndexOf('"');
        }
        return stringToTrim;
    }

Он не работает с экранированными кавычками, но работает для случаев, с которыми я сталкивался до сих пор.

0 голосов
/ 22 января 2009

Это ответ на код Антона, который не работает с экранированными кавычками. Я изменил 3 места.

  1. Конструктор для StringBuilder in SplitCommandLineArguments , заменяющий любой \ " на \ r
  2. В для цикла в SplitCommandLineArguments я теперь заменяю символ \ r на \ ".
  3. Изменен метод SplitCommandLineArgument с private на public static .

public static string[] SplitCommandLineArgument( String argumentString )
{
    StringBuilder translatedArguments = new StringBuilder( argumentString ).Replace( "\\\"", "\r" );
    bool InsideQuote = false;
    for ( int i = 0; i < translatedArguments.Length; i++ )
    {
        if ( translatedArguments[i] == '"' )
        {
            InsideQuote = !InsideQuote;
        }
        if ( translatedArguments[i] == ' ' && !InsideQuote )
        {
            translatedArguments[i] = '\n';
        }
    }

    string[] toReturn = translatedArguments.ToString().Split( new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries );
    for ( int i = 0; i < toReturn.Length; i++ )
    {
        toReturn[i] = RemoveMatchingQuotes( toReturn[i] );
        toReturn[i] = toReturn[i].Replace( "\r", "\"" );
    }
    return toReturn;
}

public static string RemoveMatchingQuotes( string stringToTrim )
{
    int firstQuoteIndex = stringToTrim.IndexOf( '"' );
    int lastQuoteIndex = stringToTrim.LastIndexOf( '"' );
    while ( firstQuoteIndex != lastQuoteIndex )
    {
        stringToTrim = stringToTrim.Remove( firstQuoteIndex, 1 );
        stringToTrim = stringToTrim.Remove( lastQuoteIndex - 1, 1 ); //-1 because we've shifted the indicies left by one
        firstQuoteIndex = stringToTrim.IndexOf( '"' );
        lastQuoteIndex = stringToTrim.LastIndexOf( '"' );
    }
    return stringToTrim;
}
0 голосов
/ 29 апреля 2019

Не могу найти ничего, что мне понравилось здесь. Ненавижу портить стек с помощью magic yield для маленькой командной строки (если бы это был терабайтный поток, это была бы другая история).

Вот мой дубль, он поддерживает экранирование с двойными кавычками:

param = "15" "экран не плох" param2 = "15" экран не плох "param3 =" "param4 = / param5

результат:

param = "15" экран не плохой "

param2 = '15-дюймовый экран не плохой'

param3 = ""

param4 =

/ param5

public static string[] SplitArguments(string commandLine)
{
    List<string> args         = new List<string>();
    List<char>   currentArg   = new List<char>();
    char?        quoteSection = null; // Keeps track of a quoted section (and the type of quote that was used to open it)
    char[]       quoteChars   = new[] {'\'', '\"'};
    char         previous     = ' '; // Used for escaping double quotes

    for (var index = 0; index < commandLine.Length; index++)
    {
        char c = commandLine[index];
        if (quoteChars.Contains(c))
        {
            if (previous == c) // Escape sequence detected
            {
                previous = ' '; // Prevent re-escaping
                if (!quoteSection.HasValue)
                {
                    quoteSection = c; // oops, we ended the quoted section prematurely
                    continue;         // don't add the 2nd quote (un-escape)
                }

                if (quoteSection.Value == c)
                    quoteSection = null; // appears to be an empty string (not an escape sequence)
            }
            else if (quoteSection.HasValue)
            {
                if (quoteSection == c)
                    quoteSection = null; // End quoted section
            }
            else
                quoteSection = c; // Start quoted section
        }
        else if (char.IsWhiteSpace(c))
        {
            if (!quoteSection.HasValue)
            {
                args.Add(new string(currentArg.ToArray()));
                currentArg.Clear();
                previous = c;
                continue;
            }
        }

        currentArg.Add(c);
        previous = c;
    }

    if (currentArg.Count > 0)
        args.Add(new string(currentArg.ToArray()));

    return args.ToArray();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...