- Este guia atualiza de forma moderna os princípios tradicionais do UNIX para oferecer diretrizes de design open source voltadas à criação de CLIs boas para humanos e robustas para automação
- Uma boa CLI deve funcionar com prioridade para pessoas, mas também seguir convenções como
stdout/stderr, códigos de saída, pipes e JSON para se combinar naturalmente com outros programas - Ajuda, documentação, mensagens de erro e saída devem permitir que o usuário saiba qual é o próximo passo, e
--help, exemplos, sugestões, indicadores de progresso e explicação de status cumprem papel central - Argumentos, flags, interação, configuração e variáveis de ambiente devem considerar tanto scripts quanto o uso no terminal, e por segurança é melhor não receber valores secretos diretamente por flags ou variáveis de ambiente
- O nome da CLI, sua distribuição e até a coleta de análises não devem prejudicar a sensação de controle do usuário nem a compatibilidade de longo prazo; mesmo ao quebrar convenções, o objetivo deve ser claro
Filosofia básica do design de CLI
- Este guia é um documento open source que, com base nos princípios tradicionais do UNIX, apresenta princípios e diretrizes concretas para o design moderno de programas de linha de comando
- No passado, a linha de comando era um ambiente voltado à máquina, próximo de um REPL sobre uma plataforma de scripting, mas hoje a CLI tem forte caráter voltado às pessoas como uma UI baseada em texto para acessar várias ferramentas, sistemas e plataformas
- A linha de comando tem limitações antigas e convenções peculiares, mas pode ser usada em praticamente qualquer notebook, permite tanto uso interativo quanto automação, e muda mais lentamente do que outros componentes do sistema
- Este guia não trata de programas de terminal em tela cheia como emacs ou vim, nem depende de uma linguagem de programação ou toolchain específica
Uma CLI voltada às pessoas e combinável
- Se a CLI é uma ferramenta usada principalmente por pessoas, então é preciso considerar as pessoas primeiro
- Muitos programas de CLI são destinados a usuários humanos, mas ainda mantêm designs de interação herdados de um passado centrado em máquinas
- A filosofia UNIX, em que programas pequenos funcionam juntos por meio de interfaces limpas, continua importante
- Convenções como entrada padrão, saída padrão, erro padrão, sinais e códigos de saída tornam possível a composição entre programas
- Texto simples em linhas é fácil de passar por pipes, e JSON é útil quando são necessários dados mais estruturados
- A consistência é a chave para transformar o custo de aprendizado de uma CLI em eficiência de longo prazo
- Seguir padrões existentes permite que o usuário adivinhe comandos e opções com mais facilidade
- Quando uma convenção prejudica a usabilidade, ela pode ser quebrada com cuidado
- Uma boa CLI não deve ser silenciosa demais nem gerar saída demais; ela deve fornecer ao usuário exatamente a informação de que ele precisa
- Costuma-se dizer que a CLI tem menos capacidade de descoberta do que uma GUI, mas é possível aumentar a possibilidade de aprendizado com ajuda abrangente, exemplos, sugestões de próximos comandos e orientações de resposta a erros
Interação conversacional e robustez
- O uso da linha de comando se parece mais com uma conversa, com várias tentativas e correções, do que com uma única execução
- Para entradas incorretas, é possível sugerir correções quando viável
- É possível mostrar com clareza o estado intermediário de tarefas em várias etapas
- É possível pedir confirmação antes de operações perigosas
- A CLI deve ser realmente robusta e, ao mesmo tempo, fazer o usuário sentir que ela é robusta
- Deve lidar com entradas inesperadas de forma elegante
- Quando possível, as operações devem ser idempotentes
- Não deve exibir por padrão stack traces assustadores na saída
- É importante ter empatia para que o usuário sinta que o software está do seu lado
- Não se trata de usar emojis nem de gamificar, mas de projetar com cuidado para que o usuário tenha sucesso
- O ecossistema de terminal tem muita inconsistência e confusão, mas suas poucas restrições permitiram novas invenções
- Vale seguir padrões existentes, mas também é possível abandonar deliberadamente padrões que prejudiquem a produtividade ou a satisfação do usuário
Regras básicas que devem ser seguidas
- Sempre que possível, deve-se usar uma biblioteca de parsing de argumentos de linha de comando
- Em caso de sucesso, o código de saída deve ser
0; em caso de falha, deve retornar um valor diferente de 0- Scripts determinam o sucesso ou a falha do programa com base no código de saída
- A saída principal do comando deve ir para
stdout- Até mesmo saídas legíveis por máquina devem ir por padrão para
stdout, para que os pipes funcionem corretamente
- Até mesmo saídas legíveis por máquina devem ir por padrão para
- Mensagens para o usuário, como logs e erros, devem ir para
stderr- Ao encadear com pipes, isso evita que as mensagens se misturem à entrada do próximo comando
Design da ajuda
- Quando
-he--helpforem passados, deve-se mostrar ajuda suficiente- Subcomandos também podem ter sua própria ajuda
-hnão deve ser sobrecarregado com outro significado
- Se um comando que exige argumentos for executado sem nenhum argumento, deve-se mostrar uma ajuda concisa
- Descrição do programa
- Um ou dois exemplos de chamada
- Explicação das flags
- Orientação para usar
--helppara informações mais detalhadas
- É recomendável incluir caminhos de suporte na ajuda
- Links para site ou GitHub são comuns
- Se houver documentação na web, a ajuda deve apontar para ela
- Se houver uma página ou âncora específica para um subcomando, é útil ligar diretamente a ela
- Como os usuários tendem a recorrer a exemplos antes da documentação, é bom colocar exemplos no início da ajuda
- Pode-se mostrar primeiro casos de uso complexos, mas comuns
- Se houver muitos exemplos, o ideal é colocá-los em um comando de cheat sheet separado ou em uma página web
- A ajuda pode ser formatada para facilitar a leitura rápida
- Títulos em negrito melhoram a legibilidade
- Isso deve ser tratado de forma independente do terminal, para que caracteres de escape não apareçam literalmente
- Se o usuário digitou algo errado e for possível inferir a intenção, deve-se sugerir uma correção
brew update jqpode, por exemplo, orientar o usuário parabrew upgrade jq- Pode-se perguntar se o comando sugerido deve ser executado, mas é melhor evitar executá-lo à força
- Se um comando precisa receber entrada por
stdin, mas ostdiné um terminal interativo, ele deve mostrar ajuda imediatamente e encerrar, ou então exibir uma mensagem emstderr
Documentação
- A ajuda fornece um resumo imediato e orientação para tarefas comuns, enquanto a documentação trata com mais detalhe do objetivo da ferramenta, do que ela não pretende fazer, de como funciona e do uso completo
- Deve-se fornecer documentação baseada na web
- Ela é pesquisável e permite links para partes específicas
- A documentação web é o formato de documentação mais abrangente
- Também deve-se fornecer documentação baseada em terminal
- Ela é rapidamente acessível
- Fica sincronizada com a versão instalada da ferramenta
- Funciona mesmo sem internet
- Pode-se considerar oferecer páginas
man- Muitos usuários verificam primeiro
man mycmd - Assim como
gitenpm, é possível acessar páginasmanpor meio de um subcomandohelp
- Muitos usuários verificam primeiro
Princípios de saída
- A saída legível para humanos é o mais importante
- Um critério simples para decidir se um determinado stream de saída é voltado para leitura humana é se ele é um TTY
- Deve-se fornecer saída legível por máquina desde que isso não prejudique a usabilidade
- Um stream de linhas em texto simples é a interface universal do UNIX
- Os usuários esperam poder passar a saída para ferramentas como
grepe que tudo funcione como esperado
- Se uma saída amigável para humanos quebrar a saída amigável para máquinas, pode-se oferecer
--plain- Em saídas tabulares, dividir células em várias linhas pode quebrar a expectativa de um registro por linha
--plainfornece uma saída em formato de tabela sem alterações para uso em scripts
- Se
--jsonfor passado, deve-se emitir JSON formatado- JSON facilita lidar com estruturas de dados complexas e pode ser usado com
jqe várias ferramentas CLI para JSON - Também é adequado para conexão direta por pipe com serviços web via
curl
- JSON facilita lidar com estruturas de dados complexas e pode ser usado com
- Em caso de sucesso, deve haver saída, mas ela deve ser curta
- Comandos UNIX tradicionais muitas vezes não exibem nada quando não há problemas, mas para humanos isso pode parecer que travou
- Se for necessário não haver saída para scripts, é possível ocultar saídas não essenciais com a opção
-q
- Comandos que alteram estado devem informar ao usuário o que foi alterado
git pushé um exemplo que mostra o que está sendo feito e o novo estado do branch remoto
- Deve ser fácil ver o estado atual do sistema
git statusmostra o estado do repositório junto com dicas sobre os próximos comandos que podem ser executados
- Operações que atravessam os limites do mundo interno do programa geralmente devem ser explícitas
- Quando lê ou grava arquivos que o usuário não passou como argumento
- Quando se comunica com um servidor remoto para baixar arquivos
- As cores devem ser usadas com intenção
- Se forem usadas demais, perdem o significado e dificultam a leitura
- As cores devem ser desativadas se não for um TTY, se
NO_COLORestiver definido, seTERM=dumbestiver definido ou se--no-colorfor passado
- Não se deve exibir animações se
stdoutnão for um terminal interativo- Isso ajuda a evitar que indicadores de progresso deixem logs de CI confusos
- Ao exibir muito texto, pode-se usar um pager como
less- É melhor usar isso apenas quando
stdinoustdoutforem terminais interativos less -FIRXpode ser usado como um conjunto razoável de opções
- É melhor usar isso apenas quando
Mensagens de erro
- Se você tratar erros como documentação, poderá reduzir o tempo que os usuários gastam procurando a documentação
- Erros previsíveis devem ser capturados e reescritos para que humanos consigam entendê-los
- Ex.: informar que não é possível gravar em
file.txte que talvez seja necessáriochmod +w file.txt
- Ex.: informar que não é possível gravar em
- A relação sinal-ruído é importante
- Quanto mais saída irrelevante houver, mais difícil será para o usuário identificar o problema
- Se houver vários erros do mesmo tipo, eles podem ser agrupados sob um único cabeçalho explicativo
- É melhor colocar a informação mais importante no fim da saída
- O olhar do usuário é atraído por texto vermelho, então isso deve ser usado com moderação e propósito
- Se o erro for inesperado ou difícil de explicar, deve-se fornecer informações de debug/traceback e uma forma de enviar um bug report
- Para não sobrecarregar o usuário, os logs de debug podem ser gravados em um arquivo em vez de no terminal
- Deve ser fácil enviar um bug report
- Um exemplo é fornecer uma URL com as informações possíveis já preenchidas
Argumentos e flags
- Argumentos são parâmetros posicionais, e flags são parâmetros nomeados
- Em
cp foo bar, os caminhos de arquivo são argumentos -r,--recursive,--file foo.txtsão flags
- Em
- Sempre que possível, deve-se preferir flags a argumentos
- Exigem mais digitação, mas o significado é claro
- Fica mais fácil mudar a forma de entrada no futuro
- Toda flag deve ter um nome longo
- Como ter
-he--helpjuntos - Isso é útil para deixar o significado explícito em scripts
- Como ter
- Flags de uma letra devem ser usadas apenas para opções frequentes
- Isso ajuda a preservar o espaço de nomes curtos para flags que forem adicionadas depois
- Quando a mesma operação simples é aplicada a vários arquivos, vários argumentos são apropriados
- Um formato como
rm file1.txt file2.txt file3.txtcombina bem com globbing
- Um formato como
- Se houver dois ou mais argumentos com significados diferentes, é bem provável que o design esteja errado
- Como exceção, ações comuns e centrais como
cp <source> <destination>são tão curtas que vale a pena memorizá-las
- Como exceção, ações comuns e centrais como
- Se houver nomes de flags padronizados, eles devem ser seguidos
-a, --all-d, --debug-f, --force--json-h, --help-n, --dry-run--no-input-o, --output-p, --port-q, --quiet-u, --user--version
- Os valores padrão devem corresponder ao comportamento correto para a maioria dos usuários
- Se você presumir que os usuários vão encontrar, lembrar e usar a flag certa toda vez, a experiência da maioria piora
- Se não houver entrada, pode-se perguntar por prompt, mas o prompt não deve ser obrigatório
- Sempre deve haver uma forma de fornecer a entrada por flag ou argumento
- Se
stdinnão for um terminal interativo, o prompt deve ser ignorado e as flags ou argumentos necessários devem ser exigidos
- Operações perigosas devem pedir confirmação antes
- Em execuções interativas, pode-se pedir que o usuário digite
youyes - Em execuções não interativas, pode-se exigir
-fou--force - Em operações graves, pode-se exigir que o nome do item a ser apagado seja digitado manualmente ou fornecer uma flag como
--confirm="name-of-thing"
- Em execuções interativas, pode-se pedir que o usuário digite
- Se aceitar entrada/saída de arquivos, deve-se oferecer suporte a
-parastdinoustdout- Isso permite conectar a saída de outros comandos sem arquivos temporários
- Sempre que possível, argumentos, flags e subcomandos devem ser processados independentemente da ordem
- Porque é comum o usuário adicionar opções ao fim de um comando anterior e executá-lo novamente
- Valores secretos não devem ser lidos diretamente por flag
- Um valor de
--passwordpode aparecer na saída depsou no histórico do shell - Deve-se considerar entrada por arquivo, como
--password-file, ou porstdin
- Um valor de
Interação
- Prompts ou elementos interativos devem ser usados apenas quando
stdinfor um TTY- Durante execução em scripts ou entrada por pipe, prompts não funcionam, então deve-se informar por erro quais flags precisam ser passadas
- Se
--no-inputfor passado, não deve haver prompts nem comportamento interativo- Se a entrada for necessária, a execução deve falhar e orientar como passá-la por flag
- Ao receber uma senha, o valor digitado pelo usuário não deve ser exibido
- Isso é feito desativando o echo do terminal
- O usuário deve conseguir sair
- Mesmo se estiver parado em I/O de rede etc., Ctrl-C deve funcionar
- Se for um wrapper como SSH, tmux ou telnet, que não pode ser encerrado com Ctrl-C, a forma de sair deve ser informada claramente
Subcomandos
- Se a ferramenta for complexa o suficiente, um conjunto de subcomandos pode reduzir a complexidade
- Agrupar várias ferramentas relacionadas em um único comando pode facilitar o uso e a descoberta
- Também é bom para compartilhar flags globais, ajuda, configuração e mecanismos de armazenamento
- É necessário haver consistência entre subcomandos
- O mesmo significado deve usar o mesmo nome de flag
- O formato de saída também deve ser parecido
- Para subcomandos com vários níveis, deve-se usar um esquema de nomes consistente
- Um padrão comum é ter duas etapas, com substantivo e verbo, como em
docker container create - Tanto
substantivo verboquantoverbo substantivosão possíveis, massubstantivo verboparece ser mais comum
- Um padrão comum é ter duas etapas, com substantivo e verbo, como em
- Devem ser evitados comandos com nomes ambíguos ou muito parecidos
- Ter
updateeupgradeao mesmo tempo pode causar confusão
- Ter
Robustez
- Todos os dados inseridos pelo usuário devem ser validados
- Como dados inválidos podem ser fornecidos, é preciso verificar cedo e encerrar com um erro compreensível
- Responsividade é mais importante do que rapidez
- É bom mostrar algo ao usuário em até 100 ms
- Também é preciso exibir uma mensagem antes de fazer uma requisição de rede para não parecer que travou
- Operações demoradas devem mostrar o progresso
- Se não houver saída por algum tempo, o programa pode parecer quebrado
- Se o indicador de progresso ficar parado por muito tempo, você pode mostrar uma estimativa de tempo restante ou uma animação para indicar que o trabalho continua
- Faça processamento paralelo quando possível, mas com cuidado
- Mostrar o progresso de tarefas paralelas no shell é difícil, e a saída misturada causa confusão
- As várias barras de progresso de
docker pullsão um exemplo de como mostrar o que está acontecendo - Mesmo que, em funcionamento normal, os logs fiquem escondidos atrás da barra de progresso, em caso de erro os logs devem ser exibidos para permitir depuração
- Operações de rede devem ter timeout
- O timeout deve ser configurável e precisa ter um valor padrão razoável
- Deve ser possível se recuperar após falhas
- Após uma falha temporária, ao executar novamente com
<up>e<enter>, deve ser possível continuar do ponto em que parou
- Após uma falha temporária, ao executar novamente com
- Sempre que possível, o modelo crash-only é preferível
- Em caso de falha ou interrupção, deve ser possível encerrar imediatamente, deixando a limpeza para a próxima execução
- Os usuários podem usar a ferramenta de formas inesperadas
- Eles podem envolvê-la em scripts, usá-la em uma internet instável, executar várias instâncias ao mesmo tempo ou rodá-la em ambientes não testados
Compatibilidade futura
- Subcomandos, argumentos, flags, arquivos de configuração e variáveis de ambiente são todos interfaces e devem continuar funcionando por muito tempo
- Sempre que possível, as mudanças devem ser aditivas
- Em vez de quebrar o comportamento de uma flag existente, é possível adicionar uma nova flag
- Se uma mudança não aditiva for necessária, deve haver aviso prévio
- Ao usar uma flag descontinuada, deve-se avisar sobre a mudança futura e também informar a forma compatível com o futuro que já pode ser usada agora
- Saída para humanos geralmente pode mudar sem grandes problemas
- Os usuários devem ser orientados a usar
--plainou--jsonem scripts para obter saída estável
- Os usuários devem ser orientados a usar
- Evite subcomandos catch-all
- Tratar automaticamente qualquer subcomando desconhecido como
runpode quebrar usos existentes quando novos subcomandos forem adicionados no futuro
- Tratar automaticamente qualquer subcomando desconhecido como
- Não permita abreviações arbitrárias de subcomandos
- Se
installtambém aceitariouins, depois fica difícil adicionar outros comandos que comecem comi - Aliases devem ser explícitos e mantidos de forma estável
- Se
- Considere se o comando ainda será executado da mesma forma daqui a 20 anos
- Não crie uma bomba-relógio em que dependências externas da internet mudam ou desaparecem e fazem o comando parar de funcionar
Sinais e caracteres de controle
- Se o usuário enviar Ctrl-C, ou seja, o sinal INT, o programa deve encerrar o mais rápido possível
- Deve informar algo imediatamente, antes de começar qualquer limpeza
- O código de limpeza deve ter timeout
- Se Ctrl-C for pressionado novamente durante uma limpeza demorada, deve ser possível pular a limpeza
- O Docker Compose informa que, ao pressionar Ctrl-C mais uma vez durante o encerramento, é possível forçar a parada imediata dos contêineres
- O programa deve assumir que pode iniciar sem que a limpeza anterior tenha sido executada
Configuração e variáveis de ambiente
- A forma de configuração deve variar conforme especificidade, estabilidade e complexidade
- Configurações que mudam com frequência a cada execução são mais adequadas para flags
- Configurações relativamente estáveis que variam por usuário, projeto ou máquina são adequadas para flags e variáveis de ambiente
- Configurações estáveis para todos os usuários dentro de um projeto são adequadas para arquivos de configuração específicos do comando e versionados
- É recomendável seguir a XDG Base Directory Specification
- O objetivo é oferecer suporte a locais de configuração de uso geral, como
~/.config, reduzindo a proliferação de dotfiles no diretório home
- O objetivo é oferecer suporte a locais de configuração de uso geral, como
- Se o programa modificar automaticamente arquivos que não sejam sua própria configuração, deve pedir consentimento ao usuário e informar exatamente o que fará
- Em vez de acrescentar conteúdo a um arquivo de configuração de sistema existente, é melhor criar um novo arquivo de configuração
- A precedência de configuração deve ser aplicada da mais alta para a mais baixa
- Flags
- Variáveis de ambiente do shell em execução
- Configuração no nível do projeto
- Configuração no nível do usuário
- Configuração de todo o sistema
- Variáveis de ambiente são adequadas para comportamentos que mudam conforme o contexto em que o comando é executado
- Nomes de variáveis de ambiente devem usar apenas letras maiúsculas, números e sublinhados para máxima portabilidade, e não devem começar com número
- Sempre que possível, os valores devem ter uma única linha
- Valores com várias linhas causam problemas de usabilidade quando usados com o comando
env
- Valores com várias linhas causam problemas de usabilidade quando usados com o comando
- Não se deve ocupar de forma imprudente nomes amplamente usados
- Você pode consultar a lista de variáveis de ambiente padrão do POSIX
- Sempre que possível, verifique variáveis de ambiente genéricas
NO_COLOR,FORCE_COLORDEBUGEDITORHTTP_PROXY,HTTPS_PROXY,ALL_PROXY,NO_PROXYSHELLTERM,TERMINFO,TERMCAPTMPDIRHOMEPAGERLINES,COLUMNS
- Quando apropriado, é possível ler variáveis de ambiente de
.env- Isso é útil para variáveis que quase nunca mudam enquanto se trabalha em um diretório específico
.envnão substitui um arquivo de configuração formal- Muitas vezes ele não é armazenado no controle de código-fonte
- Só há tipo string
- Tende a ficar desorganizado
- Está mais sujeito a problemas de codificação
- É fácil conter credenciais sensíveis e material de chaves
- Valores secretos não devem ser lidos de variáveis de ambiente
- Variáveis de ambiente podem ser expostas por processos, logs,
docker inspect,systemctl showetc. - Segredos devem ser recebidos por arquivo de credenciais, pipe, socket
AF_UNIX, serviço de gerenciamento de segredos ou outro mecanismo de IPC
- Variáveis de ambiente podem ser expostas por processos, logs,
Nomes, distribuição e coleta de métricas
- O nome de um programa CLI deve ser simples e fácil de lembrar, porque os usuários o digitam com frequência
- Se for genérico demais, pode entrar em conflito com outros comandos ou confundir os usuários
- É recomendável que o nome use apenas letras minúsculas e hífens quando necessário
curlé um bom nome, enquantoDownloadURLé um exemplo ruim
- O nome deve ser curto, mas nomes curtos demais combinam com utilitários muito comuns, como
cd,lseps - Deve ser um nome fácil de digitar
- Há o caso do nome inicial do Docker Compose,
plum, que foi mudado parafigporque era desconfortável de digitar com uma mão só
- Há o caso do nome inicial do Docker Compose,
- Sempre que possível, deve ser distribuído como um binário único
- Se um binário único for difícil, deve ser distribuído de forma fácil de remover usando o instalador de pacotes padrão da plataforma
- Ferramentas específicas de linguagem, por exemplo linters de código, podem presumir que o usuário tem o interpretador dessa linguagem
- Deve ser fácil de remover
- Como é comum querer removê-lo logo após a instalação, convém colocar as instruções de remoção abaixo das instruções de instalação
- Métricas de uso podem ajudar a melhorar a ferramenta, mas usuários de CLI esperam controlar o próprio ambiente
- Não envie dados de uso ou de falhas sem consentimento
- Deve ficar claro o que é coletado, por que é coletado, quanto é anonimizado, como é anonimizado e por quanto tempo é armazenado
- Idealmente, o melhor é opt-in; se optar por coleta padrão com opt-out, isso deve ser informado claramente no site ou na primeira execução, e deve ser fácil de desativar
- Também é possível considerar alternativas à coleta analítica
- Instrumentar a documentação web
- Instrumentar downloads
- Conversar diretamente com usuários e incentivar feedback e pedidos de funcionalidades na documentação e no repositório
1 comentários
Opiniões do Hacker News
A frase “hoje em dia, a maioria nem sabe o que é uma linha de comando” está certa, mas isso também era verdade nos anos 1980, que é o período que o TFA toma como referência
A diferença é que hoje há mais pessoas do que nunca que conhecem e conseguem usar a linha de comando; o número cresceu pelo menos em uma ordem de grandeza, talvez em duas, então dá para chamar de era de ouro da CLI
Estado global e dependências atrapalham o raciocínio e, no fim, também dificultam depuração e otimização de desempenho
Quando você extrai códigos de 500, 1.000, 5.000, 10.000 linhas, às vezes 50 mil linhas, para que rodem separadamente, muita coisa fica bem mais clara
Se for bem feito, isso também pode induzir uma estrutura de Functional Core, Imperative Shell, porque uma linha de comando alinhada à filosofia Unix deve ter muitas operações sem efeitos colaterais e poucas com efeitos colaterais
Você pode criar pequenos comandos que geram e exibem o estado do sistema imediatamente antes de uma decisão ruim ser tomada, e que são efetivamente seguros para rodar até em produção
Assim fica mais fácil entregar essas ferramentas também às pessoas que precisam estar incluídas no bus factor e envolvê-las
O que alguém de uma aldeia de uma civilização isolada na Amazônia pensa de um terminal não é relevante
Para julgar uma mudança qualitativa em algo cuja quantidade cresceu, é preciso olhar para a proporção; olhar só para números absolutos produz uma frase verdadeira, mas sem significado
Por exemplo, o número absoluto de ouvintes de jazz hoje talvez seja maior do que no auge cultural do gênero, mas isso não é porque o jazz é mais popular, e sim porque há mais gente ouvindo música
Ainda assim, é difícil dizer que o jazz é mais importante e influente nos EUA hoje do que foi no auge
Gostaria que também considerassem uma opção
--dry-run, que mostra antecipadamente o que aconteceria sem fazer mudanças de fatoÉ realmente útil para aprender uma ferramenta ou verificar se você usou corretamente opções complexas e globs de arquivos antes de executar algo difícil de desfazer
--dry-runcomo padrão e exigir a flag--executepara a execução realNão sei como eu teria feito sem ele, e ele me salvou várias vezes de situações em que eu quase destruí tudo completamente
--commitpara executar de verdadePara scripts que fazem operações irreversíveis ou difíceis de reverter, esse jeito parece mais seguro
Estou confuso sobre como projetar isso nas ferramentas que estou criando
Quando você chama uma API para buscar dados, não sei como transformar isso em dry run
Devo fornecer exemplos fictícios e saídas fictícias?
do_thing.py | dry-runÉ como pedir no prompt “explique o processo passo a passo”
Não é apenas uma questão de não mostrar animações quando a saída padrão não for um terminal interativo; nunca se deve mostrar animações em stdout
O TFA era bom no geral, mas fiquei decepcionado ao procurar a parte em que explicaria a diferença entre stderr e stdout e ver que isso não acontecia
stderr não é só para erros; é o lugar para logs e para toda saída informativa, e, se for um terminal, também é a área onde animações podem aparecer
stdout deve ser a saída real e útil, e deve ser consistente independentemente de ser um terminal ou não
Por exemplo, em
echo foo | mysed 's/oo/aa/' | cat, o stdout demysedéfaa, e stderr deve receber logs como informações de versão ou o que foi encontradoNão quero brigar com a ferramenta usando grep só para obter a saída real, nem quero que o comportamento mude quando removo
| cat, dificultando a depuraçãoSe ele pediu
--help, deve ir para stdout; se pediu logs, os logs também devem ir para stdoutSe for informação não solicitada, stderr é o lugar certo
Se desse para voltar no tempo, nomes como stdin, stdout e stdext talvez tivessem feito as pessoas seguirem essa convenção
Mas como o nome é stderr, desenvolvedores pensam, de forma razoável, que é “para relatar erros”, e colocam em stdout tudo que não é erro
Ainda assim, como estrutura de programa, esse jeito é melhor; mesmo que confunda mais no começo, permite fazer coisas mais úteis com a saída, então vale a pena
Também acho difícil considerar cores como informação
Espero que evitem o conselho “use símbolos e emojis onde eles tornarem as coisas mais claras”
O yubikey-agent citado como exemplo mostra exatamente o que eu detesto em READMEs do GitHub e interfaces de usuário brincalhonas
Tecnicamente, símbolos e emojis podem ser renderizados de formas diferentes em cada terminal, tornando as mensagens confusas
Esteticamente, a tolerância a brincadeira e vivacidade varia muito de pessoa para pessoa, então devem ser usados com muita moderação e só quando você realmente souber o que está fazendo
Também gosto de saída colorida no terminal, e emojis têm cores, o que ajuda a captar rapidamente o panorama da situação
Também gosto de realce de sintaxe; sem ele, acho código muito mais difícil de ler
Nem todo mundo é assim e, assim como não espero que meu gosto seja o padrão, gostos opostos também não deveriam ser impostos como padrão
Há quem goste e quem não goste, então basta deixar desligado por padrão e permitir que o usuário escolha
Por exemplo, um para sucesso e um para falha já basta
E a informação transmitida pelo emoji deve obrigatoriamente ser repetida também em texto
Na primeira vez que vi o CLI Guidelines, parei de ler naquela seção
Desta vez li o restante também, e no geral achei bem decente
Entendo que algumas CLIs sejam muito grandes, como
aws, e precisem de aninhamento, mas é realmente frustrante seguir por uma CLI aninhadaPara a maioria dos apps, prefiro que o help despeje todas as opções e me deixe encontrar o que preciso com
lessEssa TUI é uma UI/UX pura, opcional, e passa as configurações selecionadas para um arquivo gerado separado ou para uma função
Esse arquivo ou função sempre pode ser chamado diretamente pelo terminal, e também oferece todas as opções e ajudas usadas pela TUI
Assim, também pode ser usado em scripts e é útil para usuários de vários níveis de experiência
Se você sabe qual subcomando precisa, reduzir as opções apenas ao necessário é bem útil, mas, se não sabe, pode ser doloroso
Vasculhar uma lista grande de opções com grep também é difícil, então é preciso equilíbrio
man?--helpdos subcomandos é expandido no comando pai pode ajudarPor exemplo,
git stash --helpmostrar exatamente a ajuda degit stash, incluindo cada itemA explicação de que “tradicionalmente, comandos UNIX eram escritos principalmente partindo do pressuposto de que seriam usados por outros programas” não é precisa
Originalmente, eles eram pensados sobretudo para uso interativo dentro de um shell de login
Havia programas que produziam saída em stdout (
ls,cat,find,tty,who,date) e filtros de texto silenciosos (tr,grep,cut,uniq,sort,wc), e naquela época era possível fazer tarefas básicas de computação com comandos de uma linhaProgramas complexos eram escritos em C e, depois que surgiram linguagens específicas de domínio como
sede AWK, alguns programas com bastante processamento de strings foram movidos para o shellShell não é um ambiente de programação adequado, e nunca foi pensado para esse uso
Hoje em dia, muitas vezes é melhor fazer ambos em Python ou Lua, mas shell e C são os que estão instalados de forma mais universal
A divisão em
sh,bash,ksh,cshé um bom exemploAcho bastante razoável ver o conjunto padrão de ferramentas POSIX como a biblioteca-padrão de várias linguagens de shell
A linha de comando Unix atual, por um lado, é “incrivelmente útil” e, por outro, é quebrada por design
A utilidade fica clara quando se pensa em quanto tempo levaria para escrever o exemplo abaixo em C ou Rust:
curl -sS https://go.dev/doc/devel/release | html2text | grep -o -P '\bgo\d+\.\d+\.\d+\b' | sort -V | uniq | tail -1Mas, para ver por que ela é quebrada por design, basta olhar https://news.ycombinator.com/item?id=29747034
O problema é que interfaces de linha de comando precisam ser legíveis por humanos e, ao mesmo tempo, legíveis por máquinas, e não há uma forma padrão de resolver isso
Talvez pudesse ter havido originalmente uma solução padrão para o problema de precisar ser legível por humanos e máquinas ao mesmo tempo
A Apple criou as Human Interface Guidelines para concretizar o significado das abstrações visuais da UI de desktop
Mas a linha de comando não foi criada por pessoas que pensavam como designers da Apple; foi criada por pessoas que pensavam “preguiça, impaciência e arrogância são virtudes, então como expresso o que quero com o mínimo de código?”
Na época, não foi uma escolha errada, e cada byte importava, mas essa decisão de design agora ficou cravada em uma cadeia de ferramentas que não se pode mover
Para conseguir algo melhor do que temos hoje, parece que seria preciso praticamente abandonar a cadeia de ferramentas POSIX, e é difícil imaginar construir, sobre a base atual, uma UX descobrível e conceitualmente consistente
Mesmo no mesmo momento, há muitas formas de permitir saídas diferentes
Só que precisa haver um padrão que todos sigam, e é exatamente aí que a coisa se complica
Se o problema é que quase nenhuma implementação de
lspermite terminar nomes de arquivos com caractere NUL em vez de quebra de linha, a solução parece tão óbvia que sinto que estou deixando passar algoA interface do shell rejeita isso por algum motivo?
--jsonAo dizer “não leia segredos de variáveis de ambiente”, o guia sugere arquivos de credenciais, pipes, sockets AF_UNIX, serviços de gerenciamento de segredos e outras formas de comunicação entre processos; fico curioso sobre qual dessas opções é a mais conveniente e portátil
Também fico curioso se serviços de gerenciamento de segredos são usados só no trabalho ou também em projetos pessoais
Há um texto um pouco mais aprofundado sobre como lidar com segredos na linha de comando em https://smallstep.com/blog/command-line-secrets/
Arquivos de credenciais são uma opção simples e portátil
Arquivos já têm um modelo de permissões e não dependem de serviços externos nem de APIs proprietárias
Quando um programa aceita um arquivo de credenciais, ele também fica compatível com systemd credentials
systemd credentials é mais seguro do que arquivos de credenciais não criptografados, é criptografado e também pode ser vinculado ao TPM, mas o software que usa as credenciais não precisa oferecer suporte próprio a TPM
Por exemplo, o mbsync(https://isync.sourceforge.io/mbsync.html) tem cerca de três maneiras de fornecer a senha de autenticação IMAP
Se você não configurar uma senha, aparece um prompt na execução; também é possível colocar uma senha em texto claro no arquivo de configuração, mas isso é inconveniente para compartilhar
Também é possível configurar um comando para obter a senha, delegando o processamento a um gerenciador de senhas como o pass(1)(https://www.passwordstore.org/) ou a um prompt gráfico interativo
Como é bem documentado, dá para configurar facilmente sem precisar criar algo extra por conta própria
Gerenciadores de segredos como Doppler(https://Doppler.com) ou AWS Secrets Manager(https://aws.amazon.com/secrets-manager/) têm a vantagem de proteger segredos em um local seguro e minimizar sua exposição
Eles podem até reduzir a exposição para desenvolvedores internos, ajudando a evitar vazamentos de dados que poderiam ter sido facilmente evitados
Esses vazamentos podem colocar toda a empresa em risco e estão se tornando cada vez mais comuns
Posts relacionados: Command Line Interface Guidelines - https://news.ycombinator.com/item?id=38053692 - outubro de 2023, 1 comentário
Command Line Interface Guidelines - https://news.ycombinator.com/item?id=31651161 - junho de 2022, 1 comentário
Command Line Interface Guidelines - https://news.ycombinator.com/item?id=25492119 - dezembro de 2020, 5 comentários
É muito irritante quando alguns terminais executam automaticamente um comando se houver um caractere de nova linha no fim da string colada da área de transferência
Pessoalmente, acho que uma interface de linha de comando não deveria fazer isso
bind 'set enable-bracketed-paste on'Se for incômodo, também dá para desativar
Se necessário, várias linhas também são possíveis, e ele só executa quando você pressiona Enter
O Fish shell muitas vezes tem padrões sensatos, e, fora o prompt, ainda não encontrei nada que eu realmente queira customizar
A maioria dos que usei tinha uma opção para remover quebras de linha do conteúdo da área de transferência
#e depois coloMesmo que a quebra de linha seja interpretada, a linha inteira vira um comentário, então é seguro; antes de executar de fato, basta apagar o
#no início da linha