Разделить строку, содержащую параметры командной строки, в строку [] в С#
У меня есть одна строка, содержащая параметры командной строки, которые должны быть переданы другому исполняемому файлу, и мне нужно извлечь строку [], содержащую отдельные параметры, таким же образом, что и С#, если бы команды были указаны в команде -линия. Строка [] будет использоваться при выполнении другой точки входа сборки через отражение.
Существует ли для этого стандартная функция? Или существует предпочтительный метод (регулярное выражение?) Для правильного разделения параметров? Он должен обрабатывать строки с разделителями, которые могут содержать пробелы правильно, поэтому я не могу просто разделить на.
Пример строки:
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 [], который должен быть сгенерирован.
Обновить: мне пришлось изменить ожидаемый результат, чтобы он соответствовал тому, что на самом деле сгенерировано С# (удалено лишнее) в разделенных строках)
Ответы
Ответ 1
В дополнение к хорошему и чисто управляемому решению Earwicker, для полноты картины стоит упомянуть, что Windows также предоставляет функцию CommandLineToArgvW
для разбиения строки на массив строк:
LPWSTR *CommandLineToArgvW(
LPCWSTR lpCmdLine, int *pNumArgs);
Анализирует строку командной строки Unicode и возвращает массив указателей на аргументы командной строки вместе со счетчиком таких аргументов способом, аналогичным стандартным значениям argv и argc времени выполнения C.
Пример вызова этого API из С# и распаковки результирующего массива строк в управляемом коде можно найти по адресу: " Преобразование строки командной строки в 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);
}
}
Ответ 2
Меня раздражает, что нет функции разбить строку на основе функции, которая проверяет каждый символ. Если бы это было так, вы могли бы написать это следующим образом:
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). Я считаю эту часть обычной интерпретации командной строки.
Ответ 3
Парсер командной строки 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');
}
Ответ 4
Я взял ответ от Джеффри Л. Уитледжа и немного его улучшил.
Теперь он поддерживает одинарные и двойные кавычки. Вы можете использовать кавычки в самих параметрах, используя другие типизированные кавычки.
Он также удаляет кавычки из аргументов, поскольку они не вносят вклад в информацию об аргументах.
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);
}
Ответ 5
хорошее и чистое управляемое решение 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\quot quote:
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 Aziz, который использует CommandLineToArgvW также не удалось. Он возвратил 4 элемента:
He whispered to her \
I
love
you".
Надеюсь, что это поможет кому-то искать такое решение в будущем.
Ответ 6
Google говорит: С#/.NET аргументы аргументов командной строки
Ответ 7
Environment.GetCommandLineArgs()
Ответ 8
Мне нравятся итераторы, и в настоящее время 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();
}
Ответ 9
Эта статья о проекте Code - это то, что я использовал в прошлом. Это хороший код, но он может работать.
Эта статья MSDN - единственное, что я могу найти, объясняющее, как С# анализирует аргументы командной строки.
Ответ 10
В вашем вопросе вы спросили о регулярном выражении, и я большой поклонник и пользователь из них, поэтому, когда мне нужно было сделать такой же аргумент, как и вы, я написал свое собственное регулярное выражение после того, как искал его и не нашел простого решения, Мне нравятся короткие решения, поэтому я сделал один, и вот он:
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();
Он обрабатывает пробелы и кавычки внутри кавычек и преобразует закрытые "в". Не стесняйтесь использовать код!
Ответ 11
Использование:
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 в ответе Alley, этот также поддерживает ^ escape.
Примеры:
- Это тест
- этот
- является
-
- тестовое задание
- Это тест
- этот
- это
- тестовое задание
- этот ^ "это ^" тест
- этот
- "является
- А"
- тестовое задание
- это "" "^^ тест"
Он также поддерживает несколько пробелов (разбивает аргументы только один раз на блок пробелов).
Ответ 12
Чисто управляемое решение может быть полезным. Слишком много "проблемных" комментариев для функции 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();
}
Ответ 13
О черт. Это все... Эхх. Но это законный чиновник. От Microsoft в С# для .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
.
Ответ 14
В настоящее время это код, который у меня есть:
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;
}
Он не работает с экранированными кавычками, но он работает для случаев, с которыми я сталкивался до сих пор.
Ответ 15
Это ответ на код Антона, который не работает с экранированными кавычками. Я изменил 3 места.
- Конструктор для StringBuilder в SplitCommandLineArguments, заменив любой\"на\r
- В for-loop в SplitCommandLineArguments теперь я заменяю символ \r на\".
- Изменен метод 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;
}
Ответ 16
Вы можете взглянуть на код, который я выложил вчера:
[С#] Строки и аргументы
Он разбивает имя файла + аргументы на строку []. Короткие пути, переменные среды и отсутствующие расширения файлов обрабатываются.
(Первоначально это было для UninstallString в реестре.)
Ответ 17
Попробуйте этот код:
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;
}
Это написано на португальском языке.
Ответ 18
Здесь один вкладыш, который выполняет работу (см. Одну строку, которая выполняет всю работу внутри метода 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;
}
}
}
Ответ 19
Я не думаю, что для приложений С# есть одинарные кавычки или ^ кавычки. Следующая функция работает отлично для меня:
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();
}
Ответ 20
Не могу найти ничего, что мне понравилось здесь. Ненавижу портить стек с помощью 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();
}
Ответ 21
Я реализовал конечный автомат, чтобы получить те же результаты синтаксического анализа, как если бы аргументы передавались в приложение .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();
}
}
Ответ 22
Я не уверен, что понял вас, но проблема в том, что символ, используемый в качестве разделителя, также находится внутри текста? (За исключением того, что он избежал двойного "?)
Если это так, я бы создал цикл for
и заменил бы все экземпляры, в которых <"> присутствует, на <|> (или другой" безопасный "символ, но следил за тем, чтобы он заменял только <">, а не <"">
После итерации строки я бы сделал, как раньше, разделил строку, но теперь по символу <|>.
Ответ 23
Да, строковый объект имеет встроенную функцию Split()
которая принимает один параметр, определяющий символ, который нужно искать как разделитель, и возвращает массив строк (string []) с отдельными значениями в нем.