Quais são os bons hábitos para projetar argumentos de linha de comando?

190

Ao desenvolver o aplicativo, comecei a me perguntar: como devo projetar argumentos de linha de comando?

Muitos programas estão usando fórmula como esta -argument valueou /argument value. A solução que me veio à mente foi argument:value. Eu pensei que era bom porque, sem espaços em branco, não há como os valores e argumentos serem confusos. Também é fácil dividir uma string em duas no primeiro :caractere esquerdo .

Minhas perguntas são:

  1. A -argument valuefórmula popular é melhor que argument:value(mais legível, mais fácil de escrever, sem erros, mais fácil de entender por desenvolvedores especializados)?
  2. Existem algumas regras conhecidas que devo seguir ao criar argumentos de linha de comando (exceto se funcionar, tudo bem)?

Pediu mais alguns detalhes que irei fornecer. No entanto, acho que eles não devem afetar as respostas. A questão é sobre bons hábitos em geral. Eu acho que eles são todos iguais para todos os tipos de aplicações.

Estamos trabalhando em um aplicativo que será usado em locais públicos (totens de toque, tabelas). Os aplicativos são gravados usando o Qt Quick 5 (C ++, QML, JS). Os dispositivos terão o Windows 8.1 / 10 instalado. Forneceremos interface front-end para gerenciar os dispositivos. No entanto, alguns administradores avançados podem querer configurar o aplicativo por conta própria. Não é muito importante do lado dos negócios, mas, como concordo com o que Kilian Foth disse, não quero que meu aplicativo seja um problema para o usuário. Não encontrando na Internet o que quero, perguntei aqui.


Para usuários mais avançados do Stack Exchange: Eu queria que essa pergunta fosse geral. Talvez ele se qualifique para o wiki da comunidade (não sei se a pergunta existente pode ser convertida com respostas). Como eu quero que essa pergunta seja independente do sistema operacional e da linguagem de programação, as respostas que aparecem aqui podem ser uma lição valiosa para outros desenvolvedores.

Filip Hazubski
fonte
14
Procure ferramentas populares de linha de comando. Por exemplo, o hífen único é frequentemente usado para permitir a combinação de opções. por exemplo, você pode escrever ls -ltrpara combinar as opções -l, -te -r. Os programas no estilo GNU também geralmente permitem opções baseadas em palavras com um hífen duplo como em --reversevez de -r. Há outras convenções populares como -hpara mostrar a ajuda, --para sinalizar o final de opções, especificando -como um nome de arquivo para permitir a leitura de stdin, etc.
Brandin
72
Nem -argument valuenão -argument:valuesão comuns. Comuns são -a value, -avaluee --argument=value.
Reinierpost
39
Use uma biblioteca de análise de linha de comando popular (geralmente chamada de algo como getopt(s)) onde você puder.
reinierpost
5
@ k3b Estamos trabalhando com o Qt. Como Kevin Cline disse em seu comentário , podemos usar a biblioteca já disponível. Suponho que seja multiplataforma e bem pensado. QCommandLineParser
Filip Hazubski
11
O problema com seu resumo final é que a análise de argumentos não é um problema independente de plataforma.
pydsigner

Respostas:

237

Em sistemas POSIX (por exemplo, Linux, MacOSX), pelo menos para programas possivelmente iniciados em um terminal shell (por exemplo, a maioria deles), eu recomendaria o uso das convenções de codificação GNU (que também listam nomes de argumentos comuns) e consulte as diretrizes dos utilitários do POSIX , mesmo para software proprietário:

  • sempre manuseie --versione--help (até os /bin/trueaceite !!). Eu amaldiçoo os autores de software que não entendem --help, eu os odeio (porque prog --help é o primeiro comando que estou tentando em um novo programa)! Muitas vezes, --helppode ser abreviado como-h

  • Faça com que a --helpmensagem liste todas as opções (a menos que você tenha muitas delas ... nesse caso, liste as mais comuns e se refira explicitamente a alguma manpágina ou URL) e valores padrão de opções, e talvez importantes (e específicos do programa ) variáveis ​​ambientais. Mostre essas listas de opções no erro de argumento da opção.

  • aceitar -aargumento curto (única letra) e ter algum equivalente --long-argument, então -a2 --long-argument=2, --long-argument 2; é claro que você pode ter (para opções raramente usadas) algum --only-long-argumentnome; para argumentos modais sem opções extras -cfgeralmente é tratado como -c -fetc., portanto, sua -argument:valueproposta é estranha e eu não recomendo fazer isso.

  • usar GLIBC getopt_long ou melhor (por exemplo argp_parse , em OCaml é Argmódulo , ...)

  • geralmente use -para entrada ou saída padrão (se você não puder fazer isso, manipule /dev/stdine /dev/stdoutaté nos poucos sistemas operacionais que não os possuem)

  • imitar o comportamento de programas similares, reutilizando a maioria de suas convenções de opções; em particular -npara corrida a seco (à la make), -hajuda, -vverbosidade, etc ...

  • use --como separador entre opções e arquivo ou outros argumentos

  • se o seu programa usa isattypara testar se stdin é um terminal (e se comportar "interativamente" nesse caso), forneça uma opção para forçar o modo não interativo, da mesma forma se o seu programa tiver uma interface GUI (e for testada getenv("DISPLAY")na área de trabalho do X11), mas também puder ser usado em lote ou linha de comando.

  • Alguns programas (por exemplo gcc) aceitam listas de argumentos indiretos, o que @somefile.txtsignifica ler argumentos do programa em somefile.txt; isso pode ser útil quando seu programa pode aceitar muitos argumentos (mais que os do seu kernel ARG_MAX)

BTW, você pode até adicionar alguns recursos de preenchimento automático para o seu programa e shells habituais (como bashou zsh)

Alguns comandos antigos do Unix (por exemplo dd, ou mesmo sed) têm argumentos de comando estranhos para compatibilidade histórica. Eu recomendaria não seguir seus maus hábitos (a menos que você esteja fazendo uma variante melhor deles).

Se o seu software é uma série de programas de linha de comando relacionados, ter inspiração de git (que você certamente usar como uma ferramenta de desenvolvimento), que aceita git helpe git --helpe têm muitos gitsubcommandegitsubcommand--help

Em casos raros, você também pode usar argv[0](usando links simbólicos no seu programa), por exemplo, bashinvocado por rbashter um comportamento diferente ( shell restrito ). Mas eu geralmente não recomendo fazer isso; pode fazer sentido se o seu programa puder ser usado como intérprete de script usando shebang, isto é, #!na primeira linha interpretada por execve (2) . Se você fizer esses truques, certifique-se de documentá-los, inclusive nas --helpmensagens.

Lembre-se de que, no POSIX, o shell apresenta argumentos esquisitos ( antes de executar seu programa!), Portanto, evite exigir caracteres (como *ou $ou ~) em opções que precisariam ser escapadas do shell.

Em alguns casos, você pode incorporar um intérprete como GNU guile ou Lua em seu software (evite inventar sua própria linguagem de script Turing-completa se você não for especialista em linguagens de programação). Isso tem profundas conseqüências no design do seu software (por isso, deve-se pensar no início!). Você deve passar facilmente algum script ou expressão para esse intérprete. Se você adotar essa abordagem interessante, projete seu software e suas primitivas interpretadas com cuidado; você pode ter um usuário estranho codificando grandes scripts para sua coisa.

Em outros casos, convém permitir que usuários avançados carreguem seus plug - ins no software (usando técnicas de carregamento dinâmico à la dlopen& dlsym). Novamente, essa é uma decisão de design muito importante (portanto, defina e documente a interface do plug-in com cuidado) e você precisará definir uma convenção para passar as opções do programa para esses plug-ins.

Se o seu software for algo complexo, aceite alguns arquivos de configuração (além ou substitua os argumentos do programa) e provavelmente tenha alguma maneira de testar (ou apenas analisar) esses arquivos de configuração sem executar todo o código. Por exemplo, um agente de transferência de correio (como Exim ou Postfix) é bastante complexo e é útil poder executá-lo "pela metade" (por exemplo, observar como ele está lidando com um determinado endereço de email sem realmente enviar um email).


Observe que /optioné uma coisa do Windows ou VMS. Seria insano em sistemas POSIX (porque a hierarquia de arquivos usa /como um separador de diretório e porque o shell faz o globbing). Toda a minha resposta é principalmente para Linux (e POSIX).


PS Se possível, torne seu programa um software livre , você obterá melhorias de alguns usuários e desenvolvedores (e adicionar uma nova opção de programa geralmente é uma das coisas mais fáceis de se adicionar a um software livre existente). Além disso, sua pergunta depende muito do público - alvo : um jogo para adolescentes ou um navegador para a avó provavelmente não precisa do mesmo tipo e quantidade de opções que um compilador, ou um inspetor de rede para administradores de sistemas de datacenter ou um software CAD para microprocessador arquitetos ou para projetistas de pontes. Um engenheiro familiarizado com programação e script provavelmente gosta muito mais de ter muitas opções ajustáveis ​​do que a sua avó e provavelmente pode querer executar seu aplicativo sem o X11 (talvez em um crontabemprego).

Basile Starynkevitch
fonte
18
Concordou, mas git é um exemplo melhor. Eu não vou recomendar mesmo olhando para cvsem 2016.
Basile Starynkevitch
8
+ em caso de opção inválida, apenas mostre ajuda. É MUITO irritante receber uma mensagem de erro inútil, por exemplo, para dd -h.
domen
8
Os programas GNU suportam --helpcomo regra, mas geralmente não reconhecem o -hque é irritante. Normalmente, digite -h quando esqueço uma opção para um programa, é irritante ter que redigitar o comando com a opção mais longa --help. Afinal, digitei -h porque esqueci alguma coisa. Por que o usuário deve se lembrar de quais programas requerem '--help' e quais programas exigem '-h' para exibir a tela de ajuda? Basta incluir uma opção para -h e --help.
Brandin
30
A primeira parte desta resposta é boa, mas em algum lugar ao longo do caminho você fica tangente. Por exemplo, arquivos de configuração, plugins e dlopen, opções de licenças de software e navegadores da Web não estão mais relacionados às convenções de uma interface de linha de comando.
Brandin
5
E por favor. Se você formatar sua saída para tela, adicione uma substituição de formato de saída. Nada me incomoda mais do que comandos que truncam ou quebram linhas quando estou tentando usá-las em um script.
Sobrique
68

O fato de uma convenção de formato de dados ser popular é sua vantagem.

Você pode ver facilmente que usar = ou: ou mesmo '' como separador são diferenças triviais que podem ser convertidas entre si por computador com pouco esforço. O que seria um grande esforço para um ser humano se lembrar "Agora, veja, esse programa pouco usado delimita as coisas com :ou com =? Hmmm ..."

Em outras palavras, pelo amor de Deus, não se desvie de convenções extremamente arraigadas sem uma razão convincente. As pessoas se lembrarão do seu programa como "aquele com a sintaxe estranha e irritante do cmdline" em vez de "aquele que salvou meu ensaio da faculdade".

Kilian Foth
fonte
19
para quase todos os idiomas, existem funções de biblioteca para lidar com argumentos de linha de comando. Use um deles.
precisa saber é o seguinte
9
Bons conselhos, mas quais são essas convenções? -1
RubberDuck
14
Há um exemplo interessante de uma ferramenta UNIX comumente usada que viola esta convenção intencionalmente: ddusa uma key=valuesintaxe. O motivo dessa decisão de design é que essa ferramenta (apelido: d ata d estroyer) pode causar muitos danos quando usada incorretamente. Ao forçar o usuário a abandonar seu hábito habitual, força o usuário a pensar mais de perto sobre o que está fazendo.
Philipp
18
Tem certeza de que é esse o motivo dd? Eu acredito que ele simplesmente foi codificado em uma época (década de 1970?) Quando o ARG_MAX do kernel era pequeno, os shells não tinham auto-conclusões e --long-argumentsnão existiam. Desde então, melhor ddpermaneceu compatível com versões anteriores
Basile Starynkevitch 15/01
9
ddoriginou-se de outro sistema operacional (que não o UNIX) - uma linguagem de script (JCL) no OS / 360 da IBM - em que as convenções eram diferentes, antes de serem portadas mais ou menos inalteradas para o UNIX - em parte porque os que provavelmente o usavam o conheciam pelo sistema anterior.
Baard Kopperud
29

Em termos leigos

Quando em Roma, faça como os romanos.

  • Se o seu aplicativo CLI for destinado ao Linux / Unix, use a convenção -p valueou --parameter value. O Linux possui ferramentas para analisar esses parâmetros e sinalizadores de maneira fácil.

Eu costumo fazer algo assim:

while [[ $# > 0 ]]
do
key="$1"
case $key in
    --dummy)
    #this is a flag do something here
    ;;
    --audit_sessiones)
    #this is a flag do something here
    ;;
    --destination_path)
    # this is a key-value parameter
    # the value is always in $2 , 
    # you must shift to skip over for the next iteration
    path=$2
    shift
    ;;
    *)
    # unknown option
    ;;
esac
shift
done
  • Se o seu aplicativo CLI for destinado ao Windows, use /flage /flag:valueconvenções.

  • Alguns aplicativos como o Oracle não usam nenhum deles. Utilitários Oracle usam PARAMETER=VALUE.

  • Uma coisa que eu gosto de fazer é, além de aceitar parâmetros na linha de comando, fornecer a opção de usar um parfile , que é um arquivo de par de valores-chave para evitar longas cadeias de parâmetros. Para isso, você deve fornecer um --parfile mifile.parparâmetro adicional . Obviamente, se --parfilefor usado, todos os outros parâmetros serão descartados em favor do que está dentro do parfile.

  • Uma sugestão adicional é permitir o uso de algumas variáveis ​​de ambiente personalizadas , por exemplo, definir variáveis ​​de ambiente MYAPP_WRKSPACE=/tmptornaria desnecessário sempre definir --wrkspace /tmp.

  • No Linux, não se esqueça de adicionar o parâmetro de preenchimento automático , o que significa que os usuários podem digitar metade de um switch, pressionar TABe, em seguida, o shell o concluirá para eles.
Tulains Córdova
fonte
11
Bem, vários utilitários GNU (por exemplo gcc) lidam @mifile.parcom suas --parfile mifile.parsugestões.
Basile Starynkevitch
7
Tem certeza de que ignora todas as outras opções se houver um arquivo de parâmetro? Parece contra-intuitivo para mim, na melhor das hipóteses.
precisa
8
Eu concordo com Jonathan sobre arquivos de parâmetros. Se o arquivo de parâmetro estiver presente, eu esperaria que ele fosse usado para valores padrão, com argumentos dados na linha de comando aplicados sobre os do parfile. Se, por algum motivo, o uso de um parfile impedir o uso de argumentos adicionais da linha de comando, a presença de argumentos adicionais deverá ser um erro.
Queijo Eldritch
@EldritchCheese Nos aplicativos CLI que escrevi, quando um parfile é fornecido, qualquer parâmetro adicional gera um erro.
Tulains Córdova
11
Se você estiver usando um shell compatível, esse tipo de opção "parâmetro do arquivo de configuração" não é necessário. por exemplo, você acabou de escrever fooCmd -opt1 -opt2 $(cat more_options.opts). Portanto, eu esperaria que uma opção "parâmetro do arquivo de configuração", se fornecida, funcionasse basicamente da mesma maneira.
Brandin
19

Uma coisa que ainda não surgiu:

Tente projetar seu software a partir dos argumentos da linha de comando para cima. Significado:

Antes de projetar a funcionalidade, projete a interface do usuário.

Isso permitirá que você faça uma pesquisa detalhada de casos extremos e casos comuns desde o início. É claro que você ainda abstrairá o exterior e o interior, mas isso dará um resultado muito melhor do que apenas escrever todo o código e, em seguida, bater uma CLI nele.

Além disso, consulte o docopt ( http://docopt.org/ ).

O docopt é uma grande ajuda para isso em muitas linguagens, especialmente para python, onde você tem limitadores severos de analisadores de argumentos adversos ao usuário, como o argparse, ainda sendo considerado "OK". Em vez de ter parsers e subparsers e dicts condicionais, você apenas define a ajuda da sintaxe e faz o resto.

Florian Heigl
fonte
Adoro esta resposta e agradeço por ela, porque atualmente estou frustrado por argparsenão ser um programador, interface de usuário ou amigável.
gato
Eu tentei docopt, e eu não gosto disso. click resulta em um código muito mais limpo, embora exista um pouco de dificuldade com as opções quando você tem subcomandos.
Jpmc26
2
Isto é muito parecido com um anúncio publicitário. Mencione o recurso apenas uma vez, se for relevante. Mas, como é agora, 50% de sua resposta parece promover um recurso externo.
Brandin
Eu chamaria isso de 'projetar o modelo de uso primeiro'. Separar a experiência do usuário da funcionalidade pode ser uma distinção artificial em muitos casos (as limitações da ferramenta afetam a interface).
copper.hat
11
+1 para Docopt. Resolveu todos os meus dilemas de CLI, sem dor. Às vezes é difícil dizer anúncio de entusiasmo genuíno, mas aqui ele é - eu tenho sido um entusiasta Docopt há anos, não filiados em qualquer maneira;)
frnhr
3

Alguns comentários valiosos já foram fornecidos (@Florian, Basile), mas deixe-me acrescentar ... OP diz:

Forneceremos interface front-end para gerenciar os dispositivos. No entanto, alguns administradores avançados podem querer configurar o aplicativo por conta própria

Mas também observa:

Eu não queria que essa pergunta fosse específica de plataforma ou idioma

Você deve considerar seu público-alvo - administradores avançados . Em que plataforma eles normalmente trabalham - Win / Unix / Mac? E em qual plataforma você trabalha? Siga as convenções da CLI já estabelecidas para essa plataforma. Seus administradores "avançados" desejam / precisam de uma ferramenta baseada em GUI?

Você deseja que a interface seja consistente internamente e com outras ferramentas administrativas. Não quero parar e pensar que é cmd -p <arg>ou cmd -p:<arg>ou não cmd /p <arg>. Preciso de aspas porque existe um espaço? Posso cmd -p <val1> <val2>ou cmd -p <val1> -p <val2>para vários destinos? Eles são pedidos específicos? Sobrecarregável? Será que cmd -p2 <arg> -p1 <arg>trabalhar também? Será que ls -l -r -t dir1 dir2== ls -trl dir1 dir2?

Para minhas ferramentas administrativas do Unix, sempre lembrei das orientações fornecidas pelo Shelldorado de Heiner, além das outras referências mencionadas.

Tão importante quanto projetar a CLI é garantir que seu aplicativo seja projetado para funcionar com argumentos de linha de comando iguais aos da GUI - ou seja: nenhuma lógica de negócios na GUI ou use o comando comum chamado da GUI e da CLI.

A maioria das ferramentas administrativas baseadas em UNIX são projetadas primeiro como ferramentas de linhas de comando e a GUI fornecida simplesmente facilita o "preenchimento" das opções da linha de comando. Essa abordagem permite a automação, o uso de arquivos de resposta etc. e o gerenciamento manual (menos trabalho para mim!)

Como conjunto de ferramentas clássico usado com essa abordagem é Tcl / Tk . Não estou sugerindo que você troque de ferramenta; basta considerar a abordagem de design ao escrever um aplicativo administrativo baseado em GUI no aplicativo como uma ferramenta de linha de comando; em seguida, coloque a GUI na parte superior por conveniência. Em algum momento, você provavelmente descobrirá que a GUI é problemática (e propensa a erros) se precisar fazer várias configurações e inserir novamente geralmente as mesmas opções repetidas vezes e procurar uma abordagem automatizada.

Lembre-se de que seu administrador provavelmente precisará digitar os valores corretos nas caixas corretas de qualquer maneira; portanto, quanto esforço você está aliviando com a GUI?

Ian W
fonte
Ótimo sim! Uma pergunta também é: posso executar ./this-script --hosts <hosts.txt #
Florian Heigl