У меня есть единственная строка, содержащая параметры командной строки для передачи в другой исполняемый файл, и мне нужно извлечь строку [], содержащую отдельные параметры, так же, как это сделал бы 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"
};
Мне не нужна библиотека синтаксического анализа командной строки, просто способ получить String [], который должен быть сгенерирован.
Обновление : мне пришлось изменить ожидаемый результат, чтобы он соответствовал тому, что на самом деле генерируется C # (удалил лишние символы в разделенных строках)
источник
Ответы:
В дополнение к хорошему и чисто управляемому решению от Earwicker , для полноты картины , возможно, стоит упомянуть, что Windows также предоставляет
CommandLineToArgvW
функцию для разбиения строки на массив строк:Пример вызова этого 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); } }
источник
CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
Меня раздражает, что нет функции для разделения строки на основе функции, которая проверяет каждый символ. Если бы был, то можно было бы написать так:
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:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam", @"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""abcdefg@hijkl.com""", @"tasks:""SomeTask,Some Other Task""", @"-someParam");
Обратите внимание, что реализация имеет дополнительную функцию, заключающуюся в том, что она удаляет кавычки вокруг аргумента, если это имеет смысл (благодаря функции TrimMatchingQuotes). Я считаю, что это часть обычной интерпретации командной строки.
источник
char.IsWhiteSpace
вместо== ' '
Синтаксический анализатор командной строки 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'); }
источник
Я взял ответ Джеффри Уитледжа и немного улучшил его.
Теперь он поддерживает как одинарные, так и двойные кавычки. Вы можете использовать кавычки в самих параметрах, используя другие типизированные кавычки.
Он также удаляет кавычки из аргументов, поскольку они не вносят вклад в информацию об аргументе.
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); }
источник
Хорошее и чистое управляемое решение по 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\".");
Также отмечено , что принятый ответ на Atif Азиз , который использует 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
aRegex("(?<!\\\\)\\\"")
и использовать его вот так .Environment.GetCommandLineArgs ()
источник
Я как итераторы, и в настоящее время LINQ делает
IEnumerable<String>
так же легко использовать в качестве массивов строки, поэтому мое взятие следуя духу ответа Jeffrey L Whitledge в (как метод расширения до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(); }
источник
В своем вопросе вы запросили регулярное выражение, и я большой поклонник и пользователь его, поэтому, когда мне нужно было сделать тот же аргумент, что и вам, я написал свое собственное регулярное выражение после поиска в 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();
Он обрабатывает пробелы и кавычки внутри кавычек и преобразует заключенные в "" в ". Не стесняйтесь использовать код!
источник
Черт возьми. Это все ... Эх. Но это законно официально. От 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
.источник
Эта статья в The Code Project я использовал в прошлом. Это неплохой код, но он может сработать.
Эта статья MSDN - единственное, что я смог найти, объясняющее, как C # разбирает аргументы командной строки.
источник
Поскольку мне нужно было такое же поведение, как у 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:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo", new[] { @"/src:C:\tmp\Some Folder\Sub Folder", @"/users:abcdefg@hijkl.com", @"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(); }
Код, который я использовал для создания результатов теста, можно найти здесь
источник
Чисто управляемое решение могло бы быть полезным. Слишком много "проблемных" комментариев к функции WINAPI, и она недоступна на других платформах. Вот мой код, который имеет четко определенное поведение (которое вы можете изменить, если хотите).
Он должен делать то же самое, что и .NET / Windows при предоставлении этого
string[] args
параметра, и я сравнил его с рядом «интересных» значений.Это классическая реализация конечного автомата, которая берет каждый отдельный символ из входной строки и интерпретирует его для текущего состояния, создавая вывод и новое состояние. Состояние определяется в переменных
escape
,inQuote
,hadQuote
иprevCh
, а выход собирается вcurrentArg
иargs
.Некоторые особенности, которые я обнаружил в ходе экспериментов в реальной командной строке (Windows 7):
\\
производит\
,\"
производит"
, в""
пределах указанного диапазона производит"
.^
Характер , кажется волшебным, тоже: он всегда исчезает , когда не удваивая ее. В противном случае это не повлияет на реальную командную строку. Моя реализация не поддерживает это, поскольку я не нашел закономерности в этом поведении. Может, кто-нибудь знает об этом побольше.Что-то, что не вписывается в этот шаблон, - это следующая команда:
cmd /c "argdump.exe "a b c""
cmd
Команда , кажется, поймать внешние кавычки и взять остальное дословно. В этом должен быть какой-то особый волшебный соус.Я не тестировал свой метод, но считаю его достаточно быстрым. Он не использует
Regex
и не выполняет конкатенацию строк, а вместо этого использует aStringBuilder
для сбора символов для аргумента и помещает их в список./// <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(); }
источник
Использование:
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 in the Alley , этот также поддерживает ^ побеги.
Примеры:
Он также поддерживает несколько пробелов (разбивает аргументы только один раз на блок пробелов).
источник
В настоящее время это код, который у меня есть:
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; }
источник
Я не думаю, что для приложений 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(); }
источник
Вы можете взглянуть на код, который я опубликовал вчера:
[C #] Строки пути и аргументов
Он разбивает имя файла + аргументы на строку []. Обрабатываются короткие пути, переменные среды и отсутствующие расширения файлов.
(Первоначально это было для UninstallString в реестре.)
источник
Попробуйте этот код:
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; }
Написано на португальском.
источник
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; } } }
источник
Не могу найти здесь ничего, что мне понравилось. Ненавижу портить стек магией yield для небольшой командной строки (если бы это был поток размером в терабайт, это была бы другая история).
Вот мой вывод, он поддерживает экранирование кавычек с помощью таких двойных кавычек:
результат:
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'); }
источник
Есть пакет NuGet, который содержит именно те функции, которые вам нужны:
Microsoft.CodeAnalysis.Common содержит класс CommandLineParser с методом SplitCommandLineIntoArguments .
Вы используете это так:
using Microsoft.CodeAnalysis; // [...] var cli = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" 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:"abcdefg@hijkl.com" // tasks:"SomeTask,Some Other Task" // -someParam // foo
источник
Я не уверен, понял ли я вас, но проблема в том, что символ, используемый в качестве разделителя, также находится внутри текста? (За исключением этого экранирования двойным "?)
Если так, я бы создал
for
цикл и заменил все экземпляры, где присутствует <">, на <|> (или другой« безопасный »символ, но убедитесь, что он заменяет только <">, а не <"">После итерации строки я бы сделал, как было сказано ранее, разделил строку, но теперь по символу <|>.
источник
Да, строковый объект имеет встроенную функцию, вызываемую,
Split()
которая принимает единственный параметр, определяющий символ, который нужно искать, в качестве разделителя и возвращает массив строк (string []) с отдельными значениями в нем.источник