Melhor maneira de analisar argumentos de linha de comando em c #? [fechadas]

731

Ao criar aplicativos de console que usam parâmetros, você pode usar os argumentos passados ​​para Main(string[] args).

No passado, eu simplesmente indexei / fiz um loop nessa matriz e fiz algumas expressões regulares para extrair os valores. No entanto, quando os comandos ficam mais complicados, a análise pode ficar bem feia.

Então, eu estou interessado em:

  • Bibliotecas que você usa
  • Padrões que você usa

Suponha que os comandos sempre sigam padrões comuns, como os respondidos aqui .

Paul Stovell
fonte
Uma discussão anterior, string-contendo-parâmetros-da-linha-de-comando-em-string-em-c # , pode ter algumas respostas.
gimel
1
Oi, desculpe, é um pouco fora de tópico. no entanto, eu uso as "Configurações do aplicativo" para passar o argumento para o aplicativo. Achei bastante fácil de usar, sem necessidade de escrever a análise de argumentos / arquivos e sem necessidade de biblioteca extra. msdn.microsoft.com/en-us/library/aa730869(VS.80).aspx
me ligue Steve
44
@ me ligue para Steve: o ponto dos argumentos da linha de comando é que eles podem variar por chamada - como você faz isso com as configurações do aplicativo?
Reinierpost

Respostas:

324

Eu sugeriria fortemente o uso de NDesk.Options ( Documentation ) e / ou Mono.Options (mesma API, namespace diferente). Um exemplo da documentação :

bool show_help = false;
List<string> names = new List<string> ();
int repeat = 1;

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.\n" + 
          "this must be an integer.",
        (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 },
};

List<string> extra;
try {
    extra = p.Parse (args);
}
catch (OptionException e) {
    Console.Write ("greet: ");
    Console.WriteLine (e.Message);
    Console.WriteLine ("Try `greet --help' for more information.");
    return;
}
jonp
fonte
14
O NDesk.options é ótimo, mas não parece realmente oferecer suporte a aplicativos de console com mais de um comando distinto. Se você quer isso, tente ManyConsole que se baseia em NDesk.Options: nuget.org/List/Packages/ManyConsole
Frank Schwieterman
5
Quando tenho um aplicativo com vários comandos distintos, "coloco em camadas" os OptionSets. Veja mdoc ( docs.go-mono.com/index.aspx?link=man%3amdoc%281%29 ), que possui um OptionSet "global" ( github.com/mono/mono/blo/blob/master/mcs/tools/ mdoc /… ) que delega para um OptionSet por comando (por exemplo, github.com/mono/mono/blob/master/mcs/tools/mdoc/… )
jonp
3
NDesk não funciona para mim. Poderia ler argumentos inteiros ok, mas não seqüências de caracteres. As variáveis ​​continuam recebendo os argumentos (por exemplo, 's', 'a', etc) em vez dos valores dos argumentos (por exemplo, 'serverName', 'ApplicationName'). Desistiu e usou a 'Command Line Parser Library'. OK até agora.
Jay
2
@AshleyHenderson Por um lado, é pequeno e flexível. A maioria das soluções funciona apenas com argumentos nomeados opcionais (ou seja, não pode fazer o mesmo git checkout master), ou seus argumentos não são flexíveis (ou seja, não suporta --foo 123= --foo=123= -f 123= -f=123e também -v -h= -vh).
Wernight 28/11
1
@FrankSchwieterman que deve ser sua própria resposta. E obrigado pela dica, o ManyConsole é um verdadeiro deleite, se encaixa perfeitamente para mim.
você
197

Eu realmente gosto da Biblioteca do Analisador de Linha de Comando ( http://commandline.codeplex.com/ ). Possui uma maneira muito simples e elegante de configurar parâmetros por meio de atributos:

class Options
{
    [Option("i", "input", Required = true, HelpText = "Input file to read.")]
    public string InputFile { get; set; }

    [Option(null, "length", HelpText = "The maximum number of bytes to process.")]
    public int MaximumLenght { get; set; }

    [Option("v", null, HelpText = "Print details during execution.")]
    public bool Verbose { get; set; }

    [HelpOption(HelpText = "Display this help screen.")]
    public string GetUsage()
    {
        var usage = new StringBuilder();
        usage.AppendLine("Quickstart Application 1.0");
        usage.AppendLine("Read user manual for usage instructions...");
        return usage.ToString();
    }
}
Adrian Grigore
fonte
6
Esta é a biblioteca em que me estabeleci também. Estou escrevendo aplicativos para uma grande empresa que precisa ser mantida por muitos anos - essa biblioteca é atualizada continuamente desde 2005, parece popular, é escrita por pessoas ativas na comunidade C # e é licenciada no estilo BSD, caso o apoio desaparece.
Charles Burns
Eu recomendo isso também. Meu único problema era: especificar a combinação de argumentos permitidos (por exemplo, se o argumento de movimento também deve ter argumentos de origem e destino) pode ser possível com atributos. Mas você talvez melhor fazê-lo com a lógica Argumento validador separado
Lyndon Branco
1
Eu gosto da classe Opções. Parece suportar parâmetros sem nome e sinalizadores --recursivetambém.
Wernight
2
Acabei de o testar e implementei a opção para a minha aplicação em apenas alguns minutos. É extremamente simples de usar biblioteca.
Trismegistos
3
Achei esta biblioteca muito restritiva para mim. Se você precisar de conjuntos exclusivos, não poderá definir as opções necessárias para cada conjunto, portanto, verifique-as manualmente. Você não pode definir o requisito mínimo para valores sem nome; também é necessário verificá-los manualmente. O construtor de telas de ajuda também não é flexível. Se o comportamento da biblioteca não atender às suas necessidades imediatamente, você não poderá fazer praticamente nada para alterá-lo.
Sergey Kostrukov
50

A biblioteca WPF TestApi vem com um dos melhores analisadores de linha de comando para o desenvolvimento de C #. Eu recomendo procurar no blog de Ivo Manolov na API :

// EXAMPLE #2:
// Sample for parsing the following command-line:
// Test.exe /verbose /runId=10
// This sample declares a class in which the strongly-
// typed arguments are populated
public class CommandLineArguments
{
   bool? Verbose { get; set; }
   int? RunId { get; set; }
}

CommandLineArguments a = new CommandLineArguments();
CommandLineParser.ParseArguments(args, a);
user7116
fonte
19
+1. A análise de linha de comando é algo que realmente deve vir do fornecedor (ou seja, Microsoft), e não através de uma ferramenta de terceiros, mesmo que o suporte do fornecedor seja feito de maneira geral.
Joel Coehoorn
2
Dito isto, a resposta aceita (mono) é a próxima melhor coisa.
Joel Coehoorn
6
@ Joel, que parte é tão importante que a análise de linha de comando deva ser do fornecedor? Quais são as suas razões?
greenoldman
3
@marcias: Eu acho que ele significa que ele provavelmente deveria ter sido fora da caixa ... como um monte de coisas :)
user7116
A biblioteca é enorme! Contém muito mais do que eu preciso ...
Riri
24

Veja http://github.com/mono/mono/tree/master/mcs/class/Mono.Options/

abatishchev
fonte
2
Opções NDesk tem uma API muito agradável
user35149
2
Vou acrescentar outro voto para o NDesk: ele funciona bem, não é intrusivo e está bem documentado.
Terence
1
Mono.GetOptions é muito antigo, NDesk.Options é muito melhor (ou Mono.Options, se você preferir, é da mesma classe, aqui: anonsvn.mono-project.com/source/trunk/mcs/class/Mono.Options/… )
Matt Enright
7
@ Adam Oren: minha resposta é de 1 ano e 1 mês de idade! a estrutura do mono tronco foi refatorada. Agora esse código é colocado em anonsvn.mono-project.com/viewvc/branches/mono-2-2/mcs/class/…
abatishchev
6
@ Tormod: Mono.GetOptions obsoleto, não Mono.Options. O Mono.Options ainda é mantido.
jonp
14

Parece que todo mundo tem seus próprios analisadores de linha de comando, figura que é melhor adicionar os meus também :).

http://bizark.codeplex.com/

Esta biblioteca contém um analisador de linha de comando que inicializará uma classe com os valores da linha de comando. Ele tem muitos recursos (eu venho desenvolvendo isso há muitos anos).

A partir da documentação ...

A análise de linha de comando na estrutura do BizArk possui os seguintes recursos:

  • Inicialização automática: as propriedades da classe são definidas automaticamente com base nos argumentos da linha de comando.
  • Propriedades padrão: envie um valor sem especificar o nome da propriedade.
  • Conversão de valor: usa a poderosa classe ConvertEx também incluída no BizArk para converter valores no tipo apropriado.
  • Sinalizadores booleanos: os sinalizadores podem ser especificados usando simplesmente o argumento (ex, / b para true e / b- para false) ou adicionando o valor true / false, yes / no etc.
  • Matrizes de argumento: basta adicionar vários valores após o nome da linha de comandos para definir uma propriedade definida como uma matriz. Ex, / x 1 2 3 preencherá x com a matriz {1, 2, 3} (supondo que x seja definido como uma matriz de números inteiros).
  • Alias ​​da linha de comando: uma propriedade pode suportar vários alias da linha de comando. Por exemplo, a Ajuda usa o alias?
  • Reconhecimento parcial de nome: você não precisa digitar o nome completo ou o alias, apenas o suficiente para o analisador desambiguar a propriedade / alias dos outros.
  • Suporta ClickOnce: pode inicializar propriedades mesmo quando especificadas como a sequência de consultas em uma URL para aplicativos implantados ClickOnce. O método de inicialização da linha de comando detectará se está sendo executado como ClickOnce ou não, para que seu código não precise ser alterado ao usá-lo.
  • Cria automaticamente /? ajuda: Isso inclui uma boa formatação que leva em consideração a largura do console.
  • Carregar / salvar argumentos da linha de comando em um arquivo: isso é especialmente útil se você tiver vários conjuntos grandes e complexos de argumentos da linha de comando que deseja executar várias vezes.
Brian
fonte
2
Achei o analisador de linha de comando do BizArk muito mais fácil e fluente do que outros. Altamente recomendado!
Boris Modylevsky,
9

O CLAP (analisador de argumentos da linha de comando) possui uma API utilizável e está maravilhosamente documentado. Você cria um método, anotando os parâmetros. https://github.com/adrianaisemberg/CLAP

Coronel Panic
fonte
2
É muito simples de usar e o site deles é ótimo. Sua sintaxe, no entanto, não é muito intuitiva: myapp myverb -argname argvalue(deve ter -argname) ou myapp -help(geralmente --help).
Wernight
@Noite, você pode usar o parâmetro IsDefault no verbo para que ele possa ser omitido. Não encontrei suporte para parâmetros posicionais, mas só usei parâmetros posicionais quando estava analisando a linha de comando. É muito mais claro usar argumentos nomeados seguidos pelos valores IMHO.
Loudenvier
5

Existem inúmeras soluções para esse problema. Para completar, e para fornecer a alternativa, se alguém desejar, estou adicionando esta resposta para duas classes úteis na minha biblioteca de códigos do google .

O primeiro é ArgumentList, responsável apenas pela análise dos parâmetros da linha de comando. Ele coleta pares nome-valor definidos pelas opções '/ x: y' ou '-x = y' e também coleta uma lista de entradas 'sem nome'. Seu uso básico é discutido aqui , veja a classe aqui .

A segunda parte disso é o CommandInterpreter, que cria um aplicativo de linha de comando totalmente funcional da sua classe .Net. Como um exemplo:

using CSharpTest.Net.Commands;
static class Program
{
    static void Main(string[] args)
    {
        new CommandInterpreter(new Commands()).Run(args);
    }
    //example ‘Commands’ class:
    class Commands
    {
        public int SomeValue { get; set; }
        public void DoSomething(string svalue, int ivalue)
        { ... }

Com o código de exemplo acima, você pode executar o seguinte:

Program.exe DoSomething "string value" 5

- ou -

Program.exe dosomething / ivalue = 5 -svalue: "string value"

É tão simples quanto isso ou tão complexo quanto você precisa. Você pode revisar o código fonte , visualizar a ajuda ou baixar o binário .

csharptest.net
fonte
4

Eu gosto desse , porque você pode "definir regras" para os argumentos, necessários ou não, ...

ou, se você é do Unix, pode gostar da porta GNU Getopt .NET .

Xn0vv3r
fonte
4

Você pode gostar do meu Rug.Cmd

Analisador de argumento de linha de comando expansível e fácil de usar. Alças: Bool, Mais / Menos, String, Lista de String, CSV, Enumeração.

Construídas em '/?' modo de ajuda.

Construídas em '/??' e modos de gerador de documentos '/? D'.

static void Main(string[] args) 
{            
    // create the argument parser
    ArgumentParser parser = new ArgumentParser("ArgumentExample", "Example of argument parsing");

    // create the argument for a string
    StringArgument StringArg = new StringArgument("String", "Example string argument", "This argument demonstrates string arguments");

    // add the argument to the parser 
    parser.Add("/", "String", StringArg);

    // parse arguemnts
    parser.Parse(args);

    // did the parser detect a /? argument 
    if (parser.HelpMode == false) 
    {
        // was the string argument defined 
        if (StringArg.Defined == true)
        {
            // write its value
            RC.WriteLine("String argument was defined");
            RC.WriteLine(StringArg.Value);
        }
    }
}

Edit: Este é o meu projeto e, como tal, esta resposta não deve ser vista como um endosso de terceiros. Dito isto, eu o uso para todos os programas baseados em linha de comando que escrevo, é de código aberto e é minha esperança que outros possam se beneficiar dele.

Phill Tew
fonte
Apenas para sua informação, você deve colocar um pequeno aviso de que está afiliado ao projeto Rug.Cmd (conforme mencionado nas Perguntas frequentes): stackoverflow.com/faq#promotion - Não é grande coisa, pois você está promovendo um projeto de origem, mas ainda é bom adicionar um aviso de isenção;) +1 a propósito ... parece muito bem feito.
Jason para baixo
Felicidades por apontar isso e obrigado pelo +1, assegurarei que seja mais explícito sobre minha afiliação.
Phill Tew
Não se preocupe ... existem alguns defensores por aí para esse tipo de coisa (eu não sou um deles), então eu gosto de avisar as pessoas. Novamente, normalmente não é um problema para projetos de código aberto. É principalmente para impedir que as pessoas enviem recomendações de spam para seus produtos (pagos).
Jason para baixo
3

Há um analisador de argumentos da linha de comando em http://www.codeplex.com/commonlibrarynet

Ele pode analisar argumentos usando
1. atributos
2. chamadas explícitas
3. única linha de vários argumentos OU matriz de cadeias

Ele pode lidar com coisas como as seguintes:

- configuração : Qa - startdate : $ { hoje } - região : 'New York' Settings01

É muito fácil de usar.


fonte
2

Este é um manipulador que escrevi com base na Optionsclasse Novell .

Este é destinado a aplicativos de console que executam um while (input !="exit")loop de estilo, um console interativo como um console FTP, por exemplo.

Exemplo de uso:

static void Main(string[] args)
{
    // Setup
    CommandHandler handler = new CommandHandler();
    CommandOptions options = new CommandOptions();

    // Add some commands. Use the v syntax for passing arguments
    options.Add("show", handler.Show)
        .Add("connect", v => handler.Connect(v))
        .Add("dir", handler.Dir);

    // Read lines
    System.Console.Write(">");
    string input = System.Console.ReadLine();

    while (input != "quit" && input != "exit")
    {
        if (input == "cls" || input == "clear")
        {
            System.Console.Clear();
        }
        else
        {
            if (!string.IsNullOrEmpty(input))
            {
                if (options.Parse(input))
                {
                    System.Console.WriteLine(handler.OutputMessage);
                }
                else
                {
                    System.Console.WriteLine("I didn't understand that command");
                }

            }

        }

        System.Console.Write(">");
        input = System.Console.ReadLine();
    }
}

E a fonte:

/// <summary>
/// A class for parsing commands inside a tool. Based on Novell Options class (http://www.ndesk.org/Options).
/// </summary>
public class CommandOptions
{
    private Dictionary<string, Action<string[]>> _actions;
    private Dictionary<string, Action> _actionsNoParams;

    /// <summary>
    /// Initializes a new instance of the <see cref="CommandOptions"/> class.
    /// </summary>
    public CommandOptions()
    {
        _actions = new Dictionary<string, Action<string[]>>();
        _actionsNoParams = new Dictionary<string, Action>();
    }

    /// <summary>
    /// Adds a command option and an action to perform when the command is found.
    /// </summary>
    /// <param name="name">The name of the command.</param>
    /// <param name="action">An action delegate</param>
    /// <returns>The current CommandOptions instance.</returns>
    public CommandOptions Add(string name, Action action)
    {
        _actionsNoParams.Add(name, action);
        return this;
    }

    /// <summary>
    /// Adds a command option and an action (with parameter) to perform when the command is found.
    /// </summary>
    /// <param name="name">The name of the command.</param>
    /// <param name="action">An action delegate that has one parameter - string[] args.</param>
    /// <returns>The current CommandOptions instance.</returns>
    public CommandOptions Add(string name, Action<string[]> action)
    {
        _actions.Add(name, action);
        return this;
    }

    /// <summary>
    /// Parses the text command and calls any actions associated with the command.
    /// </summary>
    /// <param name="command">The text command, e.g "show databases"</param>
    public bool Parse(string command)
    {
        if (command.IndexOf(" ") == -1)
        {
            // No params
            foreach (string key in _actionsNoParams.Keys)
            {
                if (command == key)
                {
                    _actionsNoParams[key].Invoke();
                    return true;
                }
            }
        }
        else
        {
            // Params
            foreach (string key in _actions.Keys)
            {
                if (command.StartsWith(key) && command.Length > key.Length)
                {

                    string options = command.Substring(key.Length);
                    options = options.Trim();
                    string[] parts = options.Split(' ');
                    _actions[key].Invoke(parts);
                    return true;
                }
            }
        }

        return false;
    }
}
Chris S
fonte
2

Meu favorito pessoal é http://www.codeproject.com/KB/recipes/plossum_commandline.aspx por Peter Palotas:

[CommandLineManager(ApplicationName="Hello World",
    Copyright="Copyright (c) Peter Palotas")]
class Options
{
   [CommandLineOption(Description="Displays this help text")]
   public bool Help = false;

   [CommandLineOption(Description = "Specifies the input file", MinOccurs=1)]
   public string Name
   {
      get { return mName; }
      set
      {
         if (String.IsNullOrEmpty(value))
            throw new InvalidOptionValueException(
                "The name must not be empty", false);
         mName = value;
      }
   }

   private string mName;
}
Raphael Bossek
fonte
2

Recentemente, me deparei com a implementação de análise de linha de comando FubuCore que eu realmente gosto, as razões são:

  • é fácil de usar - embora eu não tenha conseguido encontrar uma documentação para isso, a solução FubuCore também fornece um projeto contendo um bom conjunto de testes de unidade que falam mais sobre a funcionalidade do que qualquer documentação poderia
  • ele possui um design orientado a objetos, sem repetição de código ou outras coisas que eu costumava ter em meus aplicativos de análise de linha de comando
  • é declarativo: você basicamente escreve classes para os comandos e conjuntos de parâmetros e os decora com atributos para definir várias opções (por exemplo, nome, descrição, obrigatório / opcional)
  • a biblioteca até imprime um bom gráfico de uso, com base nessas definições

Abaixo está um exemplo simples de como usar isso. Para ilustrar o uso, escrevi um utilitário simples que possui dois comandos: - add (adiciona um objeto a uma lista - um objeto consiste em um nome (string), valor (int) e um sinalizador booleano) - list (lists todos os objetos adicionados atualmente)

Primeiro de tudo, escrevi uma classe Command para o comando 'add':

[Usage("add", "Adds an object to the list")]
[CommandDescription("Add object", Name = "add")]
public class AddCommand : FubuCommand<CommandInput>
{
    public override bool Execute(CommandInput input)
    {
        State.Objects.Add(input); // add the new object to an in-memory collection

        return true;
    }
}

Este comando usa uma instância CommandInput como parâmetro, então eu defino isso a seguir:

public class CommandInput
{
    [RequiredUsage("add"), Description("The name of the object to add")]
    public string ObjectName { get; set; }

    [ValidUsage("add")]
    [Description("The value of the object to add")]
    public int ObjectValue { get; set; }

    [Description("Multiply the value by -1")]
    [ValidUsage("add")]
    [FlagAlias("nv")]
    public bool NegateValueFlag { get; set; }
}

O próximo comando é 'list', que é implementado da seguinte maneira:

[Usage("list", "List the objects we have so far")]
[CommandDescription("List objects", Name = "list")]
public class ListCommand : FubuCommand<NullInput>
{
    public override bool Execute(NullInput input)
    {
        State.Objects.ForEach(Console.WriteLine);

        return false;
    }
}

O comando 'list' não possui parâmetros, então eu defini uma classe NullInput para isso:

public class NullInput { }

Tudo o que resta agora é conectar isso no método Main (), assim:

    static void Main(string[] args)
    {
        var factory = new CommandFactory();
        factory.RegisterCommands(typeof(Program).Assembly);

        var executor = new CommandExecutor(factory);

        executor.Execute(args);
    }

O programa funciona conforme o esperado, imprimindo dicas sobre o uso correto, caso algum comando seja inválido:

  ------------------------
    Available commands:
  ------------------------
     add -> Add object
    list -> List objects
  ------------------------

E um exemplo de uso para o comando 'add':

Usages for 'add' (Add object)
  add <objectname> [-nv]

  -------------------------------------------------
    Arguments
  -------------------------------------------------
     objectname -> The name of the object to add
    objectvalue -> The value of the object to add
  -------------------------------------------------

  -------------------------------------
    Flags
  -------------------------------------
    [-nv] -> Multiply the value by -1
  -------------------------------------
Cristian Lupascu
fonte
2

C # CLI é uma biblioteca de análise de argumento de linha de comando muito simples que eu escrevi. É bem documentado e de código aberto.

Bernard
fonte
Bem documentado? Onde está a documentação?
Suhas 04/10
Existe documentação interna (ou seja, na base de código), bem como documentação externa (consulte o Readme.mkdarquivo na Documentationpasta).
Bernard
Ok, comentei apressadamente. Pode ser que você possa mover seu projeto para o github e sua documentação começa automaticamente a aparecer na página inicial.
Suhas
0

Eu sugeriria a biblioteca de código aberto CSharpOptParse . Ele analisa a linha de comando e hidrata um objeto .NET definido pelo usuário com a entrada da linha de comando. Eu sempre recorro a essa biblioteca ao escrever um aplicativo de console em C #.

Stuart Lange
fonte
0

Uma classe ad hoc fácil de usar e muito simples para análise de linha de comando, que suporta argumentos padrão.

class CommandLineArgs
{
    public static CommandLineArgs I
    {
        get
        {
            return m_instance;
        }
    }

    public  string argAsString( string argName )
    {
        if (m_args.ContainsKey(argName)) {
            return m_args[argName];
        }
        else return "";
    }

    public long argAsLong(string argName)
    {
        if (m_args.ContainsKey(argName))
        {
            return Convert.ToInt64(m_args[argName]);
        }
        else return 0;
    }

    public double argAsDouble(string argName)
    {
        if (m_args.ContainsKey(argName))
        {
            return Convert.ToDouble(m_args[argName]);
        }
        else return 0;
    }

    public void parseArgs(string[] args, string defaultArgs )
    {
        m_args = new Dictionary<string, string>();
        parseDefaults(defaultArgs );

        foreach (string arg in args)
        {
            string[] words = arg.Split('=');
            m_args[words[0]] = words[1];
        }
    }

    private void parseDefaults(string defaultArgs )
    {
        if ( defaultArgs == "" ) return;
        string[] args = defaultArgs.Split(';');

        foreach (string arg in args)
        {
            string[] words = arg.Split('=');
            m_args[words[0]] = words[1];
        }
    }

    private Dictionary<string, string> m_args = null;
    static readonly CommandLineArgs m_instance = new CommandLineArgs();
}

class Program
{
    static void Main(string[] args)
    {
        CommandLineArgs.I.parseArgs(args, "myStringArg=defaultVal;someLong=12");
        Console.WriteLine("Arg myStringArg  : '{0}' ", CommandLineArgs.I.argAsString("myStringArg"));
        Console.WriteLine("Arg someLong     : '{0}' ", CommandLineArgs.I.argAsLong("someLong"));
    }
}
Martin Lütken
fonte