Страницы

Поиск по вопросам

пятница, 29 ноября 2019 г.

Как парсить аргументы командной строки?

#c_sharp #visual_studio #консоль


Пишу консольное приложение которое должно принимать более 5 параметров. При этом
есть обязательные параметры есть не обязательные, одни параметры должны работать только
с другими и т.д.. Я уже погряз в различных if..else if..else. Как облегчить себе жизнь?
Есть ли рекомендации и стандартные приёмы по работе с аргументами командной строки?
    


Ответы

Ответ 1



Если параметров много, лучше всего вынести их разбор в отдельный класс или набор классов. Когда if/else образуют сложную структуру, можно использовать «повышенную декларативность» C# и описывать структуру команд, например, так: var commandLinePattern = Command.WithName('add') .HasOption('-fileName') | Command.WithName('help') .HasParameter("CommandName"); Конечно, это потребует определённой изобретательности при проектировании, так что ниже изложу подробнее. Следующим шагом мы опишем наши команды: public interface ICommand { void Run(); } public HelpCommand : ICommand { public string CommandName { get; set; } public bool Verbose { get; set; } public int PageSize { get; set; } public void Run() { if (CommandName == "add") { Console.WriteLine("Help on the ADD command"); } . . . } } Шаблон командной строке (переменная commandLinePattern в первом примере) можно применить к конкретной строке параметров, чтобы построить экземпляр конкретного класса команды, и заполнить его свойства, использую рефлексию: var command = commandLinePattern.Parse(args); command.Run(); UPDATE Итого, получается приблизительно такой код: static class Program { private static readonly commandLinePatterm = Command.WithName('add') .HasOption('-fileName') .HasOption('-move') | Command.WithName('help') .Parameter("CommandName"); static void Main(string[] args) { var command = commandLinePattern.Parse(args); command.Run(); } } UPDATE «для новичка» Немного о том, как реализовать всю эту красоту. Наше чудо по внешнему виду и внутреннему устройству очень напоминает регулярные выражения: сначала мы описываем некий шаблон аргументов командной строки, и затем применяем его к реальным аргументам. Результатом такого применения будет объект, который можно будет выполнить. Назовём его командой. Название команда возникло не просто так — это один из паттернов проектирования, описанный в классическом труде банды четырёх. Я не знаю, какие командные строки вам приходится разбирать, поэтому сделаю несколько предположений: Программа может выполнить за один запуск одну и только одну команду. Команда идёт первой в командной строке. Команда не требует префиксов, таких как дефис или косая черта. За командой могут следовать один или несколько параметров. Параметры могут быть именованными и безымянными. Именованные параметры имеют префикс, например, дефис. Именованные параметры могут иметь значение, в этом случае оно отделяется от имени двоеточием. Приблизительно такие правила используются в большом количестве утилит, например, в архиваторах pkzip, arj и rar; в утилитах контроля версий git и hg — то есть этих двух правил действительно хвататет для широкого спектра задач. arj add archive *.c -m0 arj — это имя программы, add — команда, archive (имя архива) и *.c (что архивировать) — безымянные параметры; -m — именованный параметр (опция), 0 — значение параметра. В arj значение параметра не требуется отделять символом : или =. Мы пойдём другим путём, исключительно для из-за того, что пример демонстрационный. Такой шаблон параметра командной строки может быть описан простым классом: public class CommandLinePattern { public string Name { get; set; } public List Parameters { get; set; } public List Options { get; set; } } Например, команда help может иметь такой шаблон program Help CommandName [-Verbose] [-PageSize:num] Которому соответствует экзмепляр класса CommandLinePattern: var pattern = new CommandLinePattern { Name = "Help", Parameters = new List { "CommandName" }, Options = new List { "Verbose", "PageSize" }, }; Нам нужно решить две задачи: построить такой шаблон, и применить его. В коде выше мы воспользовались статическим классом Command для того, чтобы начать конструировать шаблон: public static class Command { public static CommandLinePattern WithName(string name) { return new CommandLinePattern { Name = name, Parameters = new List(), Options = new List(), }; } } Остальные методы конструирования разместим непосредственно в классе CommandLinePattern: public CommandLinePattern HasOption(string name) { Options.Add(name); return this; } public CommandLinePattern HasParameter(string name) { Parameters.Add(name); return this; } Благодаря конструкции return this; мы можем строить объект последовательно, увязывая методы в цепочку: var pattern = Command.WithName("Help") .HasParameter("CommandName") .HasOption("Verbose") .HasOption("PageSize"); Таким образом мы описываем шаблоны команды. Теперь решим вторую задачу — разбор командной строки и создание объекта-команды. В классе CommandLinePattern реализуем метод TryParse, который будет пробовать разобрать командную строку и в случае успеха создавать объект, реализующий такой интерфейс ICommand: public virtual bool TryParse(string[] args, out ICommand result) { result = null; // Конечно, наш шаблон не может соответствовать пустой командной строке. if (args.Length == 0) return false; // И он не может соответствовать какой-то другой команде. if (args[0] != Name) return false; var properties = new Dictionary(); var nextParameterIndex = 0; for (int i = 1; i < args.Length; i++) { if (args[i].StartsWith("-")) { var parameterWithoutHyphen = args[i].Substring(1); var nameValue = parameterWithoutHyphen.Split(':'); if (nameValue.Lenth == 1) properties.Add(nameValue[0], null); else properties.Add(nameValue[0], nameValue[1]); } else { var name = Parameters[nextParameterIndex]; nextParameterIndex++; var value = args[i]; properties.Add(name, value); } } // Для команды с имененем Help мы найдём класс HelpCommand: var className = Name + "Command"; var type = Type.GetType(className); // И создадим его экземпляр: result = (ICommand)Activator.CreateClass(type); // Теперь значения всех параметров запишем в свойства // только что созданного экземляра: foreach (var property in properties) { var name = property.Key; var value = property.Value; type.GetProperty(name) .SetValue(result, value); } return true; } public virtual ICommand Parse(string[] args) { ICommand result; if (TryParse(args, out result)) return result; throw new FormatException(); } Имейте в виду, что такая запись значений будет работать только со строковыми свойствами — в реальной программе вам потребуется конвертировать строковые значения в типы свойств. Теперь у нас уже всё готово. Мы можем написать наш разбор параметров, но «для красоты» сделаем ещё один финальный штрих. Создадим класс, который позволит объединить два шаблона и проверять сначала левый, а затем правый: public class OrCommandLinePattern : CommandLinePattern { private readonly CommandLinePattern left; private readonly CommandLinePattern right; public OrCommandLinePattern(CommandLinePattern left, CommandLinePattern right) { this.left = left; this.right = right; } public override bool TryParse(string[] args, out ICommand result) { if (left.TryParse(args, result)) return true; return right.TryParse(args, result); } } В базовый класс добавим реализацию оператора ИЛИ: public static CommandLinePattern operator |(CommandLinePattern left, CommandLinePattern right) { return new OrCommandLinePattern(left, right); } Теперь мы можем объединить несколько паттернов с помощью вертикальной черты: var commandLinePattern = Command.WithName('add') .HasOption('-fileName') | Command.WithName('help') .HasParameter("CommandName"); И в конце запускаем парсинг с помощью вызова одного единственного метода: var command = commandLinePattern.Parse(args); Результатом работы парсера будет экземпляр класса, реализующего интерфейс ICommand с заполненными свойствами. Нам остаётся только запустить его: command.Run();

Ответ 2



Отличный парсер аргументов командной строки. Единственный минус - придётся написать небольшую прослойку на F# (~20-30 строк). Действительно небольшую - можно посмотреть туториал.

Комментариев нет:

Отправить комментарий