У меня есть одна строка, содержащая параметры командной строки, которые необходимо передать другому исполняемому файлу, и мне нужно извлечь строку [], содержащую отдельные параметры, так же, как это сделал бы C#, если бы команды были указаны в командной строке. Строка [] будет использоваться при выполнении точки входа другой сборки через отражение.
Есть ли для этого стандартная функция? Или есть предпочтительный метод (регулярное выражение?) Для правильного разделения параметров? Он должен обрабатывать строки с разделителями '"', которые могут правильно содержать пробелы, поэтому я не могу просто разделить на '' '.
Пример строки:
string parameterString = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam foo";
Пример результата:
string[] parameterArray = new string[] {
@"/src:C:\tmp\Some Folder\Sub Folder",
@"/users:[email protected]",
@"tasks:SomeTask,Some Other Task",
@"-someParam",
@"foo"
};
Мне не нужна библиотека синтаксического анализа командной строки, просто способ получить String [], который должен быть сгенерирован.
Обновлять: мне пришлось изменить ожидаемый результат, чтобы он соответствовал тому, что на самом деле создается C# (удалил лишние символы в разделенных строках)
Хороший вопрос, ищу то же самое. Надеялся найти кого-нибудь, кто скажет «эй .net раскрывает это здесь ...» :) Если я наткнусь на это в какой-то момент, я отправлю его здесь, даже если ему уже 6 лет. Все еще актуальный вопрос!
В ответе ниже я создал полностью управляемую версию, так как мне тоже нужна эта функция.
Google говорит: Синтаксический анализатор аргументов командной строки C# /. NET





Да, строковый объект имеет встроенную функцию Split(), которая принимает единственный параметр, определяющий символ, который нужно искать, в качестве разделителя и возвращает массив строк (string []) с отдельными значениями в нем.
Это приведет к неправильному разделению части src: "C: \ tmp \ Some Folder \ Sub Folder".
А как насчет кавычек внутри строки, которые временно отключают разделение на пробелы?
Я не уверен, понял ли я вас, но проблема в том, что символ, используемый в качестве разделителя, также находится внутри текста? (Кроме этого, оно экранировано двойным "?"
Если это так, я бы создал цикл for и заменил все экземпляры, где присутствует <">, на <|> (или другой« безопасный »символ, но убедитесь, что он заменяет только <">, а не <"">
После итерации строки я бы сделал, как было сказано ранее, разделил строку, но теперь по символу <|>.
Двойные "" являются строковым литералом a @ "..", двойные "" внутри строки @ ".." эквивалентны \ escaped "в обычной строке
"единственное ограничение (я верю) состоит в том, что строки разделены пробелами, если только пробел не находится в блоке" ... "-> Может быть, стрелять в птицу из базуки, но поставить логическое значение, которое идет" истина " если внутри кавычки, и если пробел обнаружен внутри, пока "истина", продолжить, иначе <> = <|>
Этот Статья Code Project - это то, что я использовал в прошлом. Это неплохой код, но он может сработать.
Этот Статья MSDN - единственное, что я смог найти, что объясняет, как C# анализирует аргументы командной строки.
Я пробовал использовать рефлектор в библиотеке C#, но он переходит к собственному вызову C++, для которого у меня нет кода, и я не вижу способа вызвать его без p-вызова. Мне также не нужна библиотека синтаксического анализа командной строки, мне просто нужна строка [].
Отражение .NET тоже ни к чему не привело. Если посмотреть на Мононуклеозисточниккодпредложенный, это разделение аргументов не выполняется CLR, а, скорее, уже поступает из операционной системы. Подумайте о параметрах argc, argv основной функции C. Так что нет ничего, что можно было бы использовать, кроме API ОС.
Синтаксический анализатор командной строки Windows ведет себя так, как вы говорите, разделяя пространство, если перед ним нет незакрытой кавычки. Я бы порекомендовал написать парсер самостоятельно. Что-то вроде этого может быть:
static string[] ParseArguments(string commandLine)
{
char[] parmChars = commandLine.ToCharArray();
bool inQuote = false;
for (int index = 0; index < parmChars.Length; index++)
{
if (parmChars[index] == '"')
inQuote = !inQuote;
if (!inQuote && parmChars[index] == ' ')
parmChars[index] = '\n';
}
return (new string(parmChars)).Split('\n');
}
У меня получилось то же самое, за исключением того, что я использовал .Split (new char [] {'\ n'}, StringSplitOptions.RemoveEmptyEntries) в последней строке на случай, если между параметрами были лишние ''. Кажется, работает.
Я предполагаю, что у Windows должен быть способ избежать кавычек в параметрах ... этот алгоритм не принимает это во внимание.
Удаление пустых строк, удаление внешних кавычек и обработка экранированных кавычек оставлены в качестве упражнения для читателя.
Char.IsWhiteSpace () может здесь помочь
Это решение хорошо, если аргументы разделены одним пробелом, но не работает, если аргументы разделяются несколькими пробелами. Ссылка на правильное решение: stackoverflow.com/a/59131568/3926504
Меня раздражает, что нет функции для разделения строки на основе функции, которая проверяет каждый символ. Если бы был, то можно было бы написать так:
public static IEnumerable<string> SplitCommandLine(string commandLine)
{
bool inQuotes = false;
return commandLine.Split(c =>
{
if (c == '\"')
inQuotes = !inQuotes;
return !inQuotes && c == ' ';
})
.Select(arg => arg.Trim().TrimMatchingQuotes('\"'))
.Where(arg => !string.IsNullOrEmpty(arg));
}
Хотя, написав это, почему бы не написать необходимые методы расширения. Ладно, ты меня уговорил ...
Во-первых, моя собственная версия Split, которая принимает функцию, которая должна решать, должен ли указанный символ разбивать строку:
public static IEnumerable<string> Split(this string str,
Func<char, bool> controller)
{
int nextPiece = 0;
for (int c = 0; c < str.Length; c++)
{
if (controller(str[c]))
{
yield return str.Substring(nextPiece, c - nextPiece);
nextPiece = c + 1;
}
}
yield return str.Substring(nextPiece);
}
В зависимости от ситуации это может привести к появлению некоторых пустых строк, но, возможно, эта информация будет полезна в других случаях, поэтому я не удаляю пустые записи в этой функции.
Во-вторых (и более приземленно) небольшой помощник, который обрежет совпадающую пару кавычек в начале и в конце строки. Он более сложен, чем стандартный метод Trim - он обрезает только один символ с каждого конца, и он не будет обрезать только один конец:
public static string TrimMatchingQuotes(this string input, char quote)
{
if ((input.Length >= 2) &&
(input[0] == quote) && (input[input.Length - 1] == quote))
return input.Substring(1, input.Length - 2);
return input;
}
И я полагаю, вам тоже понадобятся тесты. Что ж, тогда ладно. Но это должно быть совсем последнее! Сначала вспомогательная функция, которая сравнивает результат разделения с ожидаемым содержимым массива:
public static void Test(string cmdLine, params string[] args)
{
string[] split = SplitCommandLine(cmdLine).ToArray();
Debug.Assert(split.Length == args.Length);
for (int n = 0; n < split.Length; n++)
Debug.Assert(split[n] == args[n]);
}
Тогда я могу написать такие тесты:
Test("");
Test("a", "a");
Test(" abc ", "abc");
Test("a b ", "a", "b");
Test("a b \"c d\"", "a", "b", "c d");
Вот тест на ваши требования:
Test(@"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam",
@"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""[email protected]""", @"tasks:""SomeTask,Some Other Task""", @"-someParam");
Обратите внимание, что в реализации есть дополнительная функция, заключающаяся в том, что она удаляет кавычки вокруг аргумента, если это имеет смысл (благодаря функции TrimMatchingQuotes). Я считаю, что это часть обычной интерпретации командной строки.
Мне пришлось снять отметку с этого ответа, потому что у меня не было ожидаемых результатов. Фактический результат не должен иметь "" в окончательном массиве
Я прихожу в Stack Overflow, чтобы уйти от требований, которые постоянно меняются! :) Вы можете использовать Replace ("\" "," ") вместо TrimMatchingQuotes (), чтобы избавиться от всех кавычек. Но Windows поддерживает \", чтобы разрешить передачу символа кавычек. Моя функция разделения не может этого сделать.
Хороший Earwicker :) Антон: Это решение, которое я пытался описать вам в моем предыдущем посте, но Earwicker сделал намного лучше, написав его;) И также значительно расширил его;)
пробел - не единственный разделительный символ для аргументов командной строки, не так ли?
@ Луис Рис - я не уверен. Если это проблема, ее довольно легко решить: используйте char.IsWhiteSpace вместо == ' '.
Я думаю, что Test ("a \" \ "", "a", "") завершится неудачно (т.е. невозможно передать пустую строку в качестве аргумента. Проверка на то, что строка пуста, должна выполняться до обрезки кавычек.
Я думаю, вы ошиблись в логике Select и Where. Сначала вы удаляете все кавычки, а затем удаляете все пустые строки. Таким образом вы удалите все пустые строки, даже те, которые были указаны в кавычках. Должно быть наоборот: сначала проверьте пустые строки, чем удалите кавычки. Таким образом, пустые строки, явно заключенные в кавычки, останутся в конечном результате.
Environment.GetCommandLineArgs ()
Полезно - но это даст вам только аргументы командной строки, отправленные текущему процессу. Требовалось получить строку [] из строки «таким же образом, как C# если команды были указаны в командной строке». Я думаю, мы могли бы использовать декомпилятор, чтобы посмотреть, как MS реализовала это, хотя ...
Как также обнаружил Джон Галлоуэй (weblogs.asp.net/jgalloway/archive/2006/09/13/…), декомпилятор не очень помогает, что возвращает нас сразу к ответу Атифа (stackoverflow.com/questions/298830/…)
В настоящее время это код, который у меня есть:
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;
}
Он не работает с экранированными кавычками, но работает в тех случаях, с которыми я сталкивался до сих пор.
Это ответ на код Антона, который не работает с экранированными кавычками. Доработал 3 места.
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;
}
Я занимаюсь той же проблемой, вы могли подумать, что в наши дни существует простое решение для строк аргументов командной строки модульного тестирования. Все, в чем я хочу быть уверен, - это поведение, которое будет результатом заданной строки аргумента командной строки. Я пока откажусь и создам модульные тесты для string [], но могу добавить несколько интеграционных тестов, чтобы скрыть это.
В дополнение к хорошее и чистое управляемое решение от Earwicker, для полноты картины, возможно, стоит упомянуть, что Windows также предоставляет функцию CommandLineToArgvW для разбиения строки на массив строк:
LPWSTR *CommandLineToArgvW( LPCWSTR lpCmdLine, int *pNumArgs);Parses a Unicode command line string and returns an array of pointers to the command line arguments, along with a count of such arguments, in a way that is similar to the standard C run-time argv and argc values.
Пример вызова этого API из C# и распаковки результирующего массива строк в управляемом коде можно найти по адресу «Преобразование строки командной строки в Args [] с помощью API CommandLineToArgvW ()». Ниже представлена чуть более простая версия того же кода:
[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);
public static string[] CommandLineToArgs(string commandLine)
{
int argc;
var argv = CommandLineToArgvW(commandLine, out argc);
if (argv == IntPtr.Zero)
throw new System.ComponentModel.Win32Exception();
try
{
var args = new string[argc];
for (var i = 0; i < args.Length; i++)
{
var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
args[i] = Marshal.PtrToStringUni(p);
}
return args;
}
finally
{
Marshal.FreeHGlobal(argv);
}
}
Эта функция требует, чтобы вы избегали обратной косой черты пути внутри кавычек. «C: \ Program Files \» должен быть «C: \ Program Files \\», чтобы это работало и правильно анализировало строку.
Также стоит отметить, что CommandLineArgvW ожидает, что первым аргументом будет имя программы, и применяемая магия синтаксического анализа не совсем такая же, если она не передана. Вы можете подделать это с помощью чего-то вроде: CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
Для полноты картины MSVCRT не использует CommandLineToArgvW () для преобразования командной строки в argc / argv. Он использует свой собственный код, который отличается. Например, попробуйте вызвать CreateProcess с этой строкой: a "b c" d e f. В main () вы получите 3 аргумента (как описано в MSDN), но комбинация CommandLineToArgvW () / GetCommandLineW () даст вам 2.
OMG, это такой беспорядок. типичный суп из РС. ничто не канонизировано, и KISS никогда не уважают в мире MS.
Я опубликовал кроссплатформенную версию реализации MSVCRT, переведенную Microsoft, и высокоточное приближение с использованием Regex. Я знаю, что это старое, но эй - тело не прокручивается.
Я взял ответ Джеффри Л. Уитледжа и немного доработал.
Теперь он поддерживает как одинарные, так и двойные кавычки. Вы можете использовать кавычки в самих параметрах, используя другие типизированные кавычки.
Он также удаляет кавычки из аргументов, поскольку они не вносят вклад в информацию об аргументе.
public static string[] SplitArguments(string commandLine)
{
var parmChars = commandLine.ToCharArray();
var inSingleQuote = false;
var inDoubleQuote = false;
for (var index = 0; index < parmChars.Length; index++)
{
if (parmChars[index] == '"' && !inSingleQuote)
{
inDoubleQuote = !inDoubleQuote;
parmChars[index] = '\n';
}
if (parmChars[index] == '\'' && !inDoubleQuote)
{
inSingleQuote = !inSingleQuote;
parmChars[index] = '\n';
}
if (!inSingleQuote && !inDoubleQuote && parmChars[index] == ' ')
parmChars[index] = '\n';
}
return (new string(parmChars)).Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
}
Мне нравятся итераторы, и в настоящее время LINQ делает IEnumerable<String> таким же простым в использовании, как массивы строк, поэтому я следую духу Ответ Джеффри Л. Уитледжа (как метод расширения для string):
public static IEnumerable<string> ParseArguments(this string commandLine)
{
if (string.IsNullOrWhiteSpace(commandLine))
yield break;
var sb = new StringBuilder();
bool inQuote = false;
foreach (char c in commandLine) {
if (c == '"' && !inQuote) {
inQuote = true;
continue;
}
if (c != '"' && !(char.IsWhiteSpace(c) && !inQuote)) {
sb.Append(c);
continue;
}
if (sb.Length > 0) {
var result = sb.ToString();
sb.Clear();
inQuote = false;
yield return result;
}
}
if (sb.Length > 0)
yield return sb.ToString();
}
Вы можете взглянуть на код, который я опубликовал вчера:
Он разбивает имя файла + аргументы на строку []. Обрабатываются короткие пути, переменные среды и отсутствующие расширения файлов.
(Первоначально это было для UninstallString в реестре.)
В своем вопросе вы запросили регулярное выражение, и я большой поклонник и пользователь его, поэтому, когда мне нужно было сделать тот же аргумент, что и вам, я написал свое собственное регулярное выражение после поиска в Google и не нашел простого решения. Мне нравятся короткие решения, поэтому я сделал одно, и вот оно:
var re = @"\G(""((""""|[^""])+)""|(\S+)) *";
var ms = Regex.Matches(CmdLine, re);
var list = ms.Cast<Match>()
.Select(m => Regex.Replace(
m.Groups[2].Success
? m.Groups[2].Value
: m.Groups[4].Value, @"""""", @"""")).ToArray();
Он обрабатывает пробелы и кавычки внутри кавычек и преобразует заключенные "" в ". Не стесняйтесь использовать код!
Использовать:
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' });
}
Основываясь на ответе Пар в переулке, этот также поддерживает экранирование ^.
Примеры:
Он также поддерживает несколько пробелов (разбивает аргументы только один раз на блок пробелов).
Последний из трех каким-то образом мешает Markdown и не отображается должным образом.
Исправлено с пробелом нулевой ширины.
Может оказаться полезным чисто управляемое решение. Слишком много "проблемных" комментариев к функции WINAPI, и она недоступна на других платформах. Вот мой код с четко определенным поведением (которое вы можете изменить, если хотите).
Он должен делать то же самое, что и .NET / Windows при предоставлении этого параметра string[] args, и я сравнил его с рядом «интересных» значений.
Это классическая реализация конечного автомата, которая берет каждый отдельный символ из входной строки и интерпретирует его для текущего состояния, создавая вывод и новое состояние. Состояние определяется в переменных escape, inQuote, hadQuote и prevCh, а выходные данные собираются в currentArg и args.
Некоторые особенности, которые я обнаружил в ходе экспериментов в реальной командной строке (Windows 7): \\ производит \, \" производит ", "" в пределах указанного диапазона производит ".
Персонаж ^ тоже кажется волшебным: он всегда исчезает, если его не удваивать. В противном случае это не повлияет на реальную командную строку. Моя реализация не поддерживает это, так как я не нашел закономерности в этом поведении. Может, кто-нибудь знает об этом побольше.
Что-то, что не вписывается в этот шаблон, - это следующая команда:
cmd /c "argdump.exe "a b c""
Команда cmd, кажется, улавливает внешние кавычки, а остальное принимает дословно. В этом должен быть какой-то особый волшебный соус.
Я не тестировал свой метод, но считаю его достаточно быстрым. Он не использует Regex и не выполняет конкатенацию строк, а вместо этого использует StringBuilder для сбора символов для аргумента и помещения их в список.
/// <summary>
/// Reads command line arguments from a single string.
/// </summary>
/// <param name = "argsString">The string that contains the entire command line.</param>
/// <returns>An array of the parsed arguments.</returns>
public string[] ReadArgs(string argsString)
{
// Collects the split argument strings
List<string> args = new List<string>();
// Builds the current argument
var currentArg = new StringBuilder();
// Indicates whether the last character was a backslash escape character
bool escape = false;
// Indicates whether we're in a quoted range
bool inQuote = false;
// Indicates whether there were quotes in the current arguments
bool hadQuote = false;
// Remembers the previous character
char prevCh = '\0';
// Iterate all characters from the input string
for (int i = 0; i < argsString.Length; i++)
{
char ch = argsString[i];
if (ch == '\\' && !escape)
{
// Beginning of a backslash-escape sequence
escape = true;
}
else if (ch == '\\' && escape)
{
// Double backslash, keep one
currentArg.Append(ch);
escape = false;
}
else if (ch == '"' && !escape)
{
// Toggle quoted range
inQuote = !inQuote;
hadQuote = true;
if (inQuote && prevCh == '"')
{
// Doubled quote within a quoted range is like escaping
currentArg.Append(ch);
}
}
else if (ch == '"' && escape)
{
// Backslash-escaped quote, keep it
currentArg.Append(ch);
escape = false;
}
else if (char.IsWhiteSpace(ch) && !inQuote)
{
if (escape)
{
// Add pending escape char
currentArg.Append('\\');
escape = false;
}
// Accept empty arguments only if they are quoted
if (currentArg.Length > 0 || hadQuote)
{
args.Add(currentArg.ToString());
}
// Reset for next argument
currentArg.Clear();
hadQuote = false;
}
else
{
if (escape)
{
// Add pending escape char
currentArg.Append('\\');
escape = false;
}
// Copy character from input, no special meaning
currentArg.Append(ch);
}
prevCh = ch;
}
// Save last argument
if (currentArg.Length > 0 || hadQuote)
{
args.Add(currentArg.ToString());
}
return args.ToArray();
}
хорошее и чистое управляемое решение от Earwicker не смог обработать такие аргументы:
Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");
Он вернул 3 элемента:
"He whispered to her \"I
love
you\"."
Итак, вот исправление для поддержки "кавычки \" escape \ "цитаты":
public static IEnumerable<string> SplitCommandLine(string commandLine)
{
bool inQuotes = false;
bool isEscaping = false;
return commandLine.Split(c => {
if (c == '\\' && !isEscaping) { isEscaping = true; return false; }
if (c == '\"' && !isEscaping)
inQuotes = !inQuotes;
isEscaping = false;
return !inQuotes && Char.IsWhiteSpace(c)/*c == ' '*/;
})
.Select(arg => arg.Trim().TrimMatchingQuotes('\"').Replace("\\\"", "\""))
.Where(arg => !string.IsNullOrEmpty(arg));
}
Протестировано с двумя дополнительными случаями:
Test("\"C:\\Program Files\"", "C:\\Program Files");
Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");
Также отмечено, что принятый ответ от Атиф Азиз, который использует CommandLineToArgvW, также не прошел. Он вернул 4 элемента:
He whispered to her \
I
love
you".
Надеюсь, это поможет кому-то, кто ищет такое решение в будущем.
Извините за некромантию, но это решение все еще пропускает такие вещи, как bla.exe aAAA"b\"ASDS\"c"dSADSD, что приводит к aAAAb"ASDS"cdSADSD, где это решение будет выводить aAAA"b"ASDS"c"dSADSD. Я мог бы подумать о замене TrimMatchingQuotes на Regex("(?<!\\\\)\\\"") и использовать его нравится.
Попробуйте этот код:
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;
}
Написано по-португальски.
документация скорее португальская
@EnamulHassan Я бы сказал, что код также на португальском языке, например posicao_ponteiro += ((fim - posicao_ponteiro)+1);.
Вот один лайнер, который выполняет свою работу (см. Одну строку, которая выполняет всю работу внутри метода 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;
}
}
}
Я не думаю, что для приложений 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();
}
О черт. Это все ... Эх. Но это законно официально. От 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.
Да, похоже, версия C# мертва. github.com/dotnet/runtime/blob/master/src/coreclr/src/utilco de /…
Ограниченное время возрождения. pastebin.com/ajhrBS4t
Не могу найти здесь ничего, что мне понравилось. Ненавижу портить стек магией yield для небольшой командной строки (если бы это был поток размером в терабайт, это была бы другая история).
Вот мой вывод, он поддерживает экранирование кавычек с помощью таких двойных кавычек:
param = "a 15"" screen isn't bad" param2='a 15" screen isn''t bad' param3 = "" param4= /param5
результат:
param = "a 15" screen isn't bad"
param2='a 15" screen isn't bad'
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();
}
Я реализовал конечный автомат так, чтобы результаты синтаксического анализа были такими же, как если бы аргументы передавались в приложение .NET и обрабатывались методом static void Main(string[] args).
public static IList<string> ParseCommandLineArgsString(string commandLineArgsString)
{
List<string> args = new List<string>();
commandLineArgsString = commandLineArgsString.Trim();
if (commandLineArgsString.Length == 0)
return args;
int index = 0;
while (index != commandLineArgsString.Length)
{
args.Add(ReadOneArgFromCommandLineArgsString(commandLineArgsString, ref index));
}
return args;
}
private static string ReadOneArgFromCommandLineArgsString(string line, ref int index)
{
if (index >= line.Length)
return string.Empty;
var sb = new StringBuilder(512);
int state = 0;
while (true)
{
char c = line[index];
index++;
switch (state)
{
case 0: //string outside quotation marks
if (c == '\\') //possible escaping character for quotation mark otherwise normal character
{
state = 1;
}
else if (c == '"') //opening quotation mark for string between quotation marks
{
state = 2;
}
else if (c == ' ') //closing arg
{
return sb.ToString();
}
else
{
sb.Append(c);
}
break;
case 1: //possible escaping \ for quotation mark or normal character
if (c == '"') //If escaping quotation mark only quotation mark is added into result
{
state = 0;
sb.Append(c);
}
else // \ works as not-special character
{
state = 0;
sb.Append('\\');
index--;
}
break;
case 2: //string between quotation marks
if (c == '"') //quotation mark in string between quotation marks can be escape mark for following quotation mark or can be ending quotation mark for string between quotation marks
{
state = 3;
}
else if (c == '\\') //escaping \ for possible following quotation mark otherwise normal character
{
state = 4;
}
else //text in quotation marks
{
sb.Append(c);
}
break;
case 3: //quotation mark in string between quotation marks
if (c == '"') //Quotation mark after quotation mark - that means that this one is escaped and can added into result and we will stay in string between quotation marks state
{
state = 2;
sb.Append(c);
}
else //we had two consecutive quotation marks - this means empty string but the following chars (until space) will be part of same arg result as well
{
state = 0;
index--;
}
break;
case 4: //possible escaping \ for quotation mark or normal character in string between quotation marks
if (c == '"') //If escaping quotation mark only quotation mark added into result
{
state = 2;
sb.Append(c);
}
else
{
state = 2;
sb.Append('\\');
index--;
}
break;
}
if (index == line.Length)
return sb.ToString();
}
}
Вот решение, которое рассматривает пробелы (одиночные или множественные пробелы) как разделитель параметров командной строки и возвращает реальные аргументы командной строки:
static string[] ParseMultiSpacedArguments(string commandLine)
{
var isLastCharSpace = false;
char[] parmChars = commandLine.ToCharArray();
bool inQuote = false;
for (int index = 0; index < parmChars.Length; index++)
{
if (parmChars[index] == '"')
inQuote = !inQuote;
if (!inQuote && parmChars[index] == ' ' && !isLastCharSpace)
parmChars[index] = '\n';
isLastCharSpace = parmChars[index] == '\n' || parmChars[index] == ' ';
}
return (new string(parmChars)).Split('\n');
}
Поскольку мне нужно было такое же поведение, как у OP (разделить строку точно так же, как это делают Windows cmd), я написал кучу тестовых примеров и протестировал опубликованные здесь ответы:
Test( 0, m, "One", new[] { "One" });
Test( 1, m, "One ", new[] { "One" });
Test( 2, m, " One", new[] { "One" });
Test( 3, m, " One ", new[] { "One" });
Test( 4, m, "One Two", new[] { "One", "Two" });
Test( 5, m, "One Two", new[] { "One", "Two" });
Test( 6, m, "One Two", new[] { "One", "Two" });
Test( 7, m, "\"One Two\"", new[] { "One Two" });
Test( 8, m, "One \"Two Three\"", new[] { "One", "Two Three" });
Test( 9, m, "One \"Two Three\" Four", new[] { "One", "Two Three", "Four" });
Test(10, m, "One=\"Two Three\" Four", new[] { "One=Two Three", "Four" });
Test(11, m, "One\"Two Three\" Four", new[] { "OneTwo Three", "Four" });
Test(12, m, "One\"Two Three Four", new[] { "OneTwo Three Four" });
Test(13, m, "\"One Two\"", new[] { "One Two" });
Test(14, m, "One\" \"Two", new[] { "One Two" });
Test(15, m, "\"One\" \"Two\"", new[] { "One", "Two" });
Test(16, m, "One\\\" Two", new[] { "One\"", "Two" });
Test(17, m, "\\\"One\\\" Two", new[] { "\"One\"", "Two" });
Test(18, m, "One\"", new[] { "One" });
Test(19, m, "\"One", new[] { "One" });
Test(20, m, "One \"\"", new[] { "One", "" });
Test(21, m, "One \"", new[] { "One", "" });
Test(22, m, "1 A=\"B C\"=D 2", new[] { "1", "A=B C=D", "2" });
Test(23, m, "1 A=\"B \\\" C\"=D 2", new[] { "1", "A=B \" C=D", "2" });
Test(24, m, "1 \\A 2", new[] { "1", "\\A", "2" });
Test(25, m, "1 \\\" 2", new[] { "1", "\"", "2" });
Test(26, m, "1 \\\\\" 2", new[] { "1", "\\\"", "2" });
Test(27, m, "\"", new[] { "" });
Test(28, m, "\\\"", new[] { "\"" });
Test(29, m, "'A B'", new[] { "'A", "B'" });
Test(30, m, "^", new[] { "^" });
Test(31, m, "^A", new[] { "A" });
Test(32, m, "^^", new[] { "^" });
Test(33, m, "\\^^", new[] { "\\^" });
Test(34, m, "^\\\\", new[] { "\\\\" });
Test(35, m, "^\"A B\"", new[] { "A B" });
// Test cases Anton
Test(36, m, @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam foo", new[] { @"/src:C:\tmp\Some Folder\Sub Folder", @"/users:[email protected]", @"tasks:SomeTask,Some Other Task", @"-someParam", @"foo" });
// Test cases Daniel Earwicker
Test(37, m, "", new string[] { });
Test(38, m, "a", new[] { "a" });
Test(39, m, " abc ", new[] { "abc" });
Test(40, m, "a b ", new[] { "a", "b" });
Test(41, m, "a b \"c d\"", new[] { "a", "b", "c d" });
// Test cases Fabio Iotti
Test(42, m, "this is a test ", new[] { "this", "is", "a", "test" });
Test(43, m, "this \"is a\" test", new[] { "this", "is a", "test" });
// Test cases Kevin Thach
Test(44, m, "\"C:\\Program Files\"", new[] { "C:\\Program Files" });
Test(45, m, "\"He whispered to her \\\"I love you\\\".\"", new[] { "He whispered to her \"I love you\"." });
«ожидаемое» значение получено в результате непосредственного тестирования его с помощью cmd.exe на моем компьютере (Win10 x64) и простой программы печати:
static void Main(string[] args) => Console.Out.WriteLine($"Count := {args.Length}\n{string.Join("\n", args.Select((v,i) => $"[{i}] => '{v}'"))}");
Вот результаты:
Solution | Failed Tests
------------------------------|-------------------------------------
Atif Aziz (749653) | 2, 3, 10, 11, 12, 14, 16, 17, 18, 26, 28, 31, 32, 33, 34, 35, 36, 37, 39, 45
Jeffrey L Whitledge (298968) | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Daniel Earwicker (298990) | 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 45
Anton (299795) | 12, 16, 17, 18, 19, 21, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
CS. (467313) | 12, 18, 19, 21, 27, 31, 32, 33, 34, 35
Vapour in the Alley (2132004) | 10, 11, 12, 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 45
Monoman (7774211) | 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
Thomas Petersson (19091999) | 2, 3, 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 39, 45
Fabio Iotti (19725880) | 1, 2, 3, 7, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 22, 23, 25, 26, 28, 29, 30, 35, 36, 37, 39, 40, 42, 44, 45
ygoe (23961658) | 26, 31, 32, 33, 34, 35
Kevin Thach (24829691) | 10, 11, 12, 14, 18, 19, 20, 21, 22, 23, 26, 27, 31, 32, 33, 34, 35, 36
Lucas De Jesus (31621370) | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45
HarryP (48008872) | 24, 26, 31, 32, 33, 34, 35
TylerY86 (53290784) | 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 41, 43, 44, 45
Louis Somers (55903304) | 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 39, 41, 43, 44, 45
user2126375 (58233585) | 5, 6, 15, 16, 17, 31, 32, 33, 34, 35
DilipNannaware (59131568) | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Mikescher (this) | -
Поскольку никакой ответ не казался правильным (по крайней мере, на основе моего варианта использования), вот мое решение, в настоящее время оно проходит все тестовые примеры (но если у кого-то есть дополнительные (неудачные) угловые случаи, прокомментируйте):
public static IEnumerable<string> SplitArgs(string commandLine)
{
var result = new StringBuilder();
var quoted = false;
var escaped = false;
var started = false;
var allowcaret = false;
for (int i = 0; i < commandLine.Length; i++)
{
var chr = commandLine[i];
if (chr == '^' && !quoted)
{
if (allowcaret)
{
result.Append(chr);
started = true;
escaped = false;
allowcaret = false;
}
else if (i + 1 < commandLine.Length && commandLine[i + 1] == '^')
{
allowcaret = true;
}
else if (i + 1 == commandLine.Length)
{
result.Append(chr);
started = true;
escaped = false;
}
}
else if (escaped)
{
result.Append(chr);
started = true;
escaped = false;
}
else if (chr == '"')
{
quoted = !quoted;
started = true;
}
else if (chr == '\\' && i + 1 < commandLine.Length && commandLine[i + 1] == '"')
{
escaped = true;
}
else if (chr == ' ' && !quoted)
{
if (started) yield return result.ToString();
result.Clear();
started = false;
}
else
{
result.Append(chr);
started = true;
}
}
if (started) yield return result.ToString();
}
Код, который я использовал для генерации результатов теста, можно найти здесь
Есть пакет NuGet, который содержит именно ту функциональность, которая вам нужна:
Microsoft.CodeAnalysis.Common содержит класс CommandLineParser с методом SplitCommandLineIntoArguments.
Вы используете это так:
using Microsoft.CodeAnalysis;
// [...]
var cli = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam foo";
var cliArgs = CommandLineParser.SplitCommandLineIntoArguments(cli, true);
Console.WriteLine(string.Join('\n', cliArgs));
// prints out:
// /src:"C:\tmp\Some Folder\Sub Folder"
// /users:"[email protected]"
// tasks:"SomeTask,Some Other Task"
// -someParam
// foo
Я написал метод отделения имени файла от его аргументов для использования с ProcessStartInfo, который требует разделения имени файла и строки аргумента.
Например "C:\Users\Me\Something.exe" -a
даст { "C:\Users\Me\Something.exe", "-a" } в результате
Код ниже:
public static string[] SplitCommandFromArgs(string commandLine)
{
commandLine = commandLine.Trim();
if (commandLine[0] == '"')
{
bool isEscaped = false;
for (int c = 1; c < commandLine.Length; c++)
{
if (commandLine[c] == '"' && !isEscaped)
{
return new string[] { commandLine.Substring(1, c - 1), commandLine.Substring(c + 1).Trim() };
}
isEscaped = commandLine[c] == '\\';
}
}
else
{
for (int c = 1; c < commandLine.Length; c++) {
if (commandLine[c] == ' ')
{
return new string[] { commandLine.Substring(0, c), commandLine.Substring(c).Trim() };
}
}
}
return new string[] { commandLine, "" };
}
Каждый раз, когда кто-то отвечает, у вас появляется возражение, основанное на материалах, которых нет в вашем сообщении. Предлагаю вам обновить свой пост этим материалом. Вы можете получить более точные ответы.