Работа с Command Line в .Net

Добрый день сообщество,

Долгое время я был просто читателем полезных технических статей.
Думаю, что пришло и мое время написать свой первый пост. Я надеюсь, что этот пост который, будет полезен не только начинающим разработчикам .net.

Мне кажется, что этой, на первый взгляд, банальной теме уделяется мало внимания. В этой статье я хотел бы поделится опытом работы с Command Line в .Net.

Почему, я выбрал эту тему, очень часто в университетах студентам рассказывают о параметрах командной строки
static void Main(string[] args)

и после чего дают задачи, которые звучат примерно так “Напишите приложение А которое будет принимать параметры X,Y,Z, произведет действие и выведите их на экран...”.
После чего, обычно, получается примерно, следующий код:
class MainClass
{
    static int Main(string[] args)
    {
        // Test if input arguments were supplied:
        if (args.Length == 0)
        {
            System.Console.WriteLine("Please enter a numeric argument.");
            System.Console.WriteLine("Usage: Factorial <num>");
            return 1;
        }
 
        try
        {
            // Convert the input arguments to numbers:
            int num = int.Parse(args[0]);
 
            System.Console.WriteLine("The Factorial of {0} is {1}.", num, Functions.Factorial(num));
            return 0;
        }
        catch (System.FormatException)
        {
            System.Console.WriteLine("Please enter a numeric argument.");
            System.Console.WriteLine("Usage: Factorial <num>");
            return 1;
        }
    }
}


Все вроде бы хорошо, но чем больше параметров, тем больше появляется строк типа
int num = int.Parse(args[Х]);
где Х это номер параметра.

В большинстве случаев люди привыкают к такому типу вычитки параметров. Придя на реальный проект, и получая задание, написать небольшую консольное приложение, которое будет при определённых параметрах что то делать, работают с параметрами тем же способом.
Но в реальных приложениях параметров командной строки, может быть великое множество, да и порядок их не должен иметь особого значения для пользователя.
Но даже во многих реальных продуктах, в крации, суть вычитки параметров командной строки сводится к работе с массовом string[] args.

Я тоже написал и видел много вариантов чтения параметров, и в один прекрасный день нашел красивую реализацию вычитки параметров, которые меня устраивает полностью это библиотека Command Line Parser Library.

Чем же она хороша?

А прелесть ее использования заключается всего в 3-х шагах:
  1. Создания пользовательского класса с набором public полей, которые будут нашими параметрами
  2. Используя атрибут Option над каждой переменной создать ее описание.
  3. Самое использование CommandLineParser.
public enum OptimizeFor
        {
            Unspecified,
            Speed,
            Accuracy
        }
 
        public sealed class Options : CommandLineOptionsBase
        {
            #region Standard Option Attribute
            [Option("r", "read",
                    Required = true,
                    HelpText = "Input file with data to process.")]
            public string InputFile = String.Empty;
 
            [Option("w", "write",
                    HelpText = "Output file with processed data (otherwise standard output).")]
            public string OutputFile = String.Empty;
 
            [Option("j", "jump",
                    HelpText = "Data processing start offset.")]
            public double StartOffset = 0;
 
            [Option(null, "optimize",
                HelpText = "Optimize for Speed|Accuracy.")]
            public OptimizeFor Optimization = OptimizeFor.Unspecified;
            #endregion
 
            #region Specialized Option Attribute
            [ValueList(typeof(List<string>))]
            public IList<string> DefinitionFiles = null;
 
            [OptionList("o", "operators", Separator = ';',
                HelpText = "Operators included in processing (+;-;...)." +
                " Separate each operator with a semicolon." +
                " Do not include spaces between operators and separator.")]
            public IList<string> AllowedOperators = null;
           
            [HelpOption(HelpText = "Dispaly this help screen.")]
            public string GetUsage()
            {
                var help = new HelpText(Program._headingInfo);
                help.AdditionalNewLineAfterOption = true;
                help.Copyright = new CopyrightInfo("Giacomo Stelluti Scala", 2005, 2009);
                this.HandleParsingErrorsInHelp(help);
                help.AddPreOptionsLine("This is free software. You may redistribute copies of it under the terms of");
                help.AddPreOptionsLine("the MIT License <http://www.opensource.org/licenses/mit-license.php>.");
                help.AddPreOptionsLine("Usage: SampleApp -rMyData.in -wMyData.out --calculate");
                help.AddPreOptionsLine(string.Format("       SampleApp -rMyData.in -i -j{0} file0.def file1.def", 9.7));
                help.AddPreOptionsLine("       SampleApp -rMath.xml -wReport.bin -o *;/;+;-");
                help.AddOptions(this);
 
                return help;
            }
 
            private void HandleParsingErrorsInHelp(HelpText help)
            {
                string errors = help.RenderParsingErrorsText(this);
                if (!string.IsNullOrEmpty(errors))
                {
                    help.AddPreOptionsLine(string.Concat(Environment.NewLine, "ERROR: ", errors, Environment.NewLine));
                }
            }        
        }

Как вы можете видеть, из примера, библиотека помогает парсить любые типа параметров, и помогает достаточно просто создавать помощь по работе с командной строкой.

Сама непосредственная работа с парсингои выглядит так:
 private static void Main(string[] args)
        {
            var options = new Options();
            ICommandLineParser parser = new CommandLineParser();
            if (!parser.ParseArguments(args, options))
                Environment.Exit(1);
 
            DoCoreTask(options);
 
            Environment.Exit(0);
        }


Как видно использование данного подхода несет в себе массу положительных моментов в виде удобочитаемости, тестируемости, поддержки т.д.

Спасибо, надеюсь, мой первый «блин» вышел не комом, а экзешником.

Ссылки на библиотеку и примеры взяты из:
MSDN: http://msdn.microsoft.com/en-us/library/cb20e19t(v=vs.80).aspx
Command Line Parser Library: Command Line Parser Library
Поделиться публикацией
Похожие публикации
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 31
  • +4
    Зачем нужен последний
    Environment.Exit(0);
    
    Всё таки не С.
    Спасибо, надеюсь мой первый «блин» вышел не комом, а экзешником.

    За бородатую шутку +1. :-)
    • 0
      Знаю что это не очень кошерно, но я отметил, что примеры в посту я не мои.
    • +1
      Много опечаток. Ну и, конечно, библиотек множество. Кому-то больше по нраву придется getopt.codeplex.com/.
      • 0
        Да верно, их много. Спасибо подправил.
      • +1
        Есть еще прекрасная штука Mono.Options:
        tirania.org/blog/archive/2008/Oct-14.html
        devlicio.us/blogs/rob_reynolds/archive/2009/11/22/command-line-parsing-with-mono-options.aspx

        Получается что-то типа такого.
        var p = new OptionSet () {
                    { "n|name=", "the name of someone to greet.",
                      v => names.Add (v) },
                    { "r|repeat=", "the number of times to repeat the greeting.",
                      (int v) => repeat = v },
                    { "v", "increase debug message verbosity",
                      v => { if (v != null) ++verbosity; } },
                    { "h|help",  "show this message and exit", 
                      v => show_help = v != null },
                }
        

        Примерно те же яйцы, но на делегатах. В Command Line Parser Library не всем может понравиться генерить и обновлять модельку под параметры.
        • 0
          «Создания пользовательского класса с набором глобальных переменных»
          Простите, а мы об одном языке говорим? Я думал, статья о c#.
          • 0
            Спасибо подбправил, согласен не корректно звучит.
            • 0
              «пользовательского класса с набором public переменных»
              Лучше не стало.

              Честное слово, вы бы хоть немного ознакомились с терминологией ООП и конкретно взятого c#.
              • 0
                Вы бы пример привели, как правильно?
                • 0
                  «класс с набором публичных полей»
                  Слово «пользовательский» не нужно, переменная не может быть публичной.

                  Кстати, за использование в публичном интерфейсе полей (а не свойств) надо бить палкой по пяткам.
                  • 0
                    да согласен со всем кроме 2 строки. «пользовательский» можно оставить.
                    • 0
                      А что, вы можете создать какой-то другой класс? И в чем отличие «пользовательского» класса от не-пользовательского?
                      • 0
                        Хороший вопрос, просто это достаточно часто употребляемый термин в литературе. Как бы подразумевается что то что ты пишешь пользовательское, а то что написал Microsoft нет — «системное». С точки зрения самого термина, ясное дело никакой разницы.
                        • 0
                          «это достаточно часто употребляемый термин в литературе»
                          Да? Покажите мне хотя бы один пример из msdn.

                          «Как бы подразумевается что то что ты пишешь пользовательское, а то что написал Microsoft нет — «системное».»
                          А то, что Microsoft написал не в рамках BCL? А то, что написали third-party vendors?
                          • 0
                            Я не уточнил, это в наших переводных изданиях (книгах) по .NET.
                            Вообще, конечно, я понимаю, что может это и звучит обидно, но все же от части все программисты пользователи, той же VS.
                            Так что это слово в данном контексте только подчеркивет суть и не более того.
                            • 0
                              «Я не уточнил, это в наших переводных изданиях (книгах) по .NET. „
                              Лишний раз много говорит о качестве переводных изданий.

                              “Вообще, конечно, я понимаю, что может это и звучит обидно, но все же от части все программисты пользователи, той же VS.»
                              Программист — пользователь VS, но порожденный им код — не пользовательский. Более того, порождаемый программистом код вполне может быть «системным» для следующего программиста, который пишет к нему «пользовательский». И что в этом случае делать?

                              Просто обратите внимание, что в msdn такое словоупотребление не встречается вовсе. Там просто пишут «your class».
                              • 0
                                Да тут унас уже получается место для философии :) ок обязательно.
                                • 0
                                  Это не философия, а азы. Разжеванные у всех, включая МакКоннела.
                                  • 0
                                    философия это к слову «пользовательский», а азы тут не причем.
                                    • 0
                                      Это как раз и есть азы, а именно выбор правильной абстракции (и, как следствие, правильного термина).
                                      • 0
                                        Конечно подравил — молодец. Но если честно, стоило ли так цепляться к терминам, тем более, я уверен что они были понятны на все 100%?
                                        Это его первый пост, а вы сразу набросились.
                                        • 0
                                          Если человека не поправить в первом посте, то во втором он сделает все те же ошибки.
                                      • 0
                                        Это терминология, то есть азы.
            • +2
              Рекомендую ещё ознакомиться вот с такой штукой. Идеология совершенно иная (построено на колбэках), но тоже весьма юзабельно. Пример кода:
                 string data = null;
                 bool help   = false;
                 int verbose = 0;
                 var p = new OptionSet () {
                 	{ "file=",      v => data = v },
                 	{ "v|verbose",  v => { ++verbose } },
                 	{ "h|?|help",   v => help = v != null },
                 };
                 List<string> extra = p.Parse (args);
              

              В данном примере распознаётся строка вида file=filename.jpg, а так же указана возможность восприятия синонимов (v|verbose).
              Тут лежит документация, всё достаточно подробно описано.
              • 0
                Да хорошое замечаение, если кто то хочет работать с Mono почему и нет!
                • +1
                  Почему обязательно с моно? Библиотека к моновскому рантайму не привязана, замечательно работает под Windows и ставится через NuGet.
              • 0
                Я использую класс отсюда. Меньше кода, меньше возможностей, но для моих маленьких консольных приложений подходит идеально.
                • 0
                  А чем не подошли NDesk.Options или Mono.Options (исходник)?
                  • 0
                    Я не говорил что они не подошли, они все по своему классные, моей идей было просто осветить эту тему. Да нужно наверно просто дописать кусочек.
                  • 0
                    Я не говорил что они не подошли, они все по своему классные, моей идей было просто осветить эту тему. Да нужно наверно просто дописать кусочек.
                    • 0
                      Все вроде бы хорошо, но чем больше параметров, тем больше появляется строк типа
                      int num = int.Parse(args[Х]);
                      

                      где Х это номер параметра.


                      Я бы просто использовал циклы.

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

                      Самое читаемое