“Regras” que os programas de terminal seguem
Recentemente estive pensando em como tudo o que acontece no terminal é uma combinação de:
- Seu sistema operacionaltrabalho
- Seu conchatrabalho
- Seu emulador de terminaltrabalho
- O trabalho de qualquer programa que você esteja executando (como
topouvimoucat)
Os três primeiros (seu sistema operacional, shell e emulador de terminal) são todos tipos de quantidades conhecidas – se você estiver usando o bash no Terminal GNOME no Linux, poderá mais ou menos raciocinar sobre como todas essas coisas interagem, e alguns de seus comportamentos são padronizados pelo POSIX.
Mas o quarto (“qualquer programa que você esteja executando”) parece que pode fazer QUALQUER COISA. Como você deve saber como um programa se comportará?
Esta postagem é meio longa, então aqui está um índice rápido:
programas se comportam de forma surpreendentemente consistente
Até onde eu sei, não existem padrões reais sobre como os programas no terminal devem se comportar – as coisas mais próximas que conheço são:
- POSIX, que determina principalmente como seu emulador de terminal/SO/shell deve funcionar em conjunto. Acho que especifica algumas coisas sobre como os principais utilitários gostam
cpdeveria funcionar, mas AFAIK não tem nada a dizer sobre como, por exemplohtopdeveria se comportar. - estas diretrizes de interface de linha de comando
Mas mesmo que não existam padrões, na minha experiência, os programas no terminal se comportam de maneira bastante consistente. Então, eu queria escrever uma lista de “regras” que, na minha experiência, os programas seguem principalmente.
estes devem ser descritivos, não prescritivos
Meu objetivo aqui não é convencer os autores de programas terminais de que eles deve
siga qualquer uma dessas regras. Existem muitas exceções a estas e muitas vezes há uma boa razão para essas exceções.
Mas é muito útil saber qual comportamento esperar de um novo programa de terminal aleatório que estou usando. Em vez de “uh, os programas podem fazer literalmente qualquer coisa”, é “ok, aqui estão as regras básicas que espero, e então posso manter uma pequena lista mental de exceções”.
Então estou apenas anotando o que observei sobre como os programas se comportam nos meus 20 anos de uso do terminal, por que acho que eles se comportam dessa maneira e alguns exemplos de casos em que essa regra é “quebrada”.
nem sempre é óbvio quais “regras” são responsabilidade do programa implementar
Há um monte de convenções comuns que eu acho que são claramente responsabilidade do programa implementar, como:
- arquivos de configuração devem entrar
~/.BLAHrcou~/.config/BLAH/FILEou/etc/BLAH/ou algo assim --helpdeve imprimir o texto de ajuda- os programas devem imprimir saída “normal” em stdout e erros em stderr
Mas neste post vou focar em coisas que não são 100% óbvias e são de responsabilidade do programa. Por exemplo, parece-me uma “lei da natureza” que pressionar Ctrl-D deveriam abandonar um REPL, mas os programas muitas vezes precisam implementar explicitamente o apoio a ele – mesmo que cat não precisa implementar Ctrl-D apoiar, ipython faz. (mais sobre isso na “regra 3” abaixo)
Compreender quais coisas são de responsabilidade do programa torna muito menos surpreendente quando as implementações de diferentes programas são ligeiramente diferentes.
regra 1: programas não interativos devem ser encerrados quando você pressiona Ctrl-C
A principal razão para esta regra é que os programas não interativos serão encerrados por padrão em Ctrl-C se eles não configurarem um SIGINT manipulador de sinal, então esta é uma espécie de regra “você deve agir como o padrão”.
Algo que confunde muita gente é que isso não se aplica a
interativo programas como python3 ou bc ou less. Isso ocorre porque em um programa interativo, Ctrl-C tem um trabalho diferente – se o programa estiver executando uma operação (como por exemplo uma pesquisa em less ou algum código Python em python3), então Ctrl-C interromperá essa operação, mas não interromperá o programa.
Como um exemplo de como isso funciona em um programa interativo: aqui está o código no prompt-toolkit (a biblioteca que o iPython usa para manipular entradas) que aborta uma pesquisa quando você pressiona Ctrl-C.
regra 2: as TUIs devem ser encerradas quando você pressiona q
Programas TUI (como less ou htop) geralmente será encerrado quando você pressionar q.
Esta regra não se aplica a nenhum programa onde pressionar q desistir não faria sentido, tipo tmux ou editores de texto.
regra 3: REPLs devem encerrar quando você pressiona Ctrl-D em uma linha vazia
REPLs (como python3 ou ed) geralmente será encerrado quando você pressionar Ctrl-D em uma linha vazia. Esta regra é semelhante à Ctrl-C regra – a razão para isso é que, por padrão, se você estiver executando um programa (como cat) em “modo cozido”, o sistema operacional retornará um EOF quando você pressiona Ctrl-D em uma linha vazia.
A maioria dos REPLs que uso (sqlite3, python3, fish, bash, etc) na verdade não usa o modo cozido, mas todos implementam esse atalho de teclado de qualquer maneira para imitar o comportamento padrão.
Por exemplo, aqui está o código no prompt-toolkit que é encerrado quando você pressiona Ctrl-D, e aqui está o mesmo código no readline.
Na verdade, eu pensei que esta era uma “Lei da Física Terminal” até muito recentemente porque basicamente nunca a vi quebrada, mas você pode ver que é apenas algo que cada biblioteca de entrada individual deve implementar nos links acima.
Alguém apontou que o Erlang REPL não fecha quando você pressiona Ctrl-Dentão acho que nem todo REPL segue essa “regra”.
regra 4: não use mais de 16 cores
Os programas de terminal raramente usam cores diferentes das 16 cores ANSI de base. Isso ocorre porque se você especificar cores com um código hexadecimal, é muito provável que entre em conflito com a cor de fundo de alguns usuários. Por exemplo, se eu imprimir algum texto como
#EEEEEEseria quase invisível em um fundo branco, mas ficaria bem em um fundo escuro.
Mas se você seguir as 16 cores básicas padrão, terá uma chance muito maior de que o usuário tenha configurado essas cores em seu emulador de terminal para que funcionem razoavelmente bem com sua cor de fundo. Outro motivo para manter a base padrão de 16 cores é que ela faz menos suposições sobre quais cores o emulador de terminal suporta.
Os únicos programas que costumo ver quebrando essa “regra” são os editores de texto, por exemplo, o Helix, por padrão, usará um fundo roxo que não é uma cor ANSI padrão. Parece bom para o Helix quebrar esta regra, já que o Helix não é um programa “principal” e presumo que qualquer usuário do Helix que não goste desse esquema de cores apenas mudará o tema.
regra 5: suporta vagamente atalhos de teclado readline
Quase todos os programas que uso suportam readline atalhos de teclado se fizer sentido fazê-lo. Por exemplo, aqui estão vários programas diferentes e um link para onde eles definem Ctrl-E para ir até o final da linha:
Nenhum desses programas realmente usa readline diretamente, eles apenas imitam os atalhos de teclado do emacs/readline. Eles nem sempre os imitam exatamente: por exemplo, atuin parece usar Ctrl-A como um prefixo, então Ctrl-A não vai para o início da linha.
Além disso, todos esses programas parecem implementar seus próprios buffers internos de recortar e colar para que você possa excluir uma linha com Ctrl-U e depois cole com Ctrl-Y.
As exceções a isso são:
- alguns programas (como
git,catenc) não têm nenhum suporte para edição de linha (exceto backspace,Ctrl-WeCtrl-U) - como de costume, os editores de texto são uma exceção, cada editor de texto tem sua própria abordagem para editar texto
Escrevi mais sobre “quais combinações de teclas um programa suporta?” A questão de inserir texto no terminal é complicada.
regra 5.1: Ctrl-W deve deletar a última palavra
Nunca vi um programa (além de um editor de texto) onde Ctrl-W não
exclua a última palavra. Isto é semelhante ao Ctrl-C regra – por padrão, se um programa estiver em “modo cozido”, o sistema operacional excluirá a última palavra se você pressionar
Ctrl-We exclua a linha inteira se você pressionar Ctrl-U. Geralmente, os programas imitam esse comportamento.
Não consigo pensar em nenhuma exceção além dos editores de texto, mas se houver, adoraria ouvir sobre eles!
regra 6: desative as cores ao escrever em um pipe
A maioria dos programas desativa as cores ao gravar em um canal. Por exemplo:
rg blahirá destacar todas as ocorrências deblahna saída, mas se a saída for para um canal ou arquivo, o realce será desativado.ls --color=autousará cores ao escrever em um terminal, mas não ao escrever em um pipe
Ambos os programas também formatarão sua saída de maneira diferente ao escrever no terminal: ls organizará os arquivos em colunas e o ripgrep agrupará as correspondências com os títulos.
Se quiser forçar o programa a usar cores (por exemplo, porque deseja ver a cor), você pode usar unbuffer para forçar a saída do programa a ser um tty assim:
unbuffer rg blah | less -R
Tenho certeza de que existem alguns programas que “quebram” essa regra, mas não consigo pensar em nenhum exemplo no momento. Alguns programas têm um --color sinalizador que você pode usar para forçar a ativação da cor, no exemplo acima você também pode fazer rg --color=always | less -R.
regra 7: - significa stdin/stdout
Geralmente se você passar - para um programa em vez de um nome de arquivo, ele lerá stdin ou gravará em stdout (o que for apropriado). Por exemplo, se você deseja formatar o código Python que está na sua área de transferência com black e copiá-lo, você pode executar:
pbpaste | black - | pbcopy
(pbpaste é um programa para Mac, você pode fazer algo semelhante no Linux com xclip)
Minha impressão é que a maioria dos programas implementa isso se fizer sentido e não consigo pensar em nenhuma exceção no momento, mas tenho certeza de que há muitas exceções.
essas “regras” levam muito tempo para serem aprendidas
Levei muito tempo para aprender essas regras porque tive que:
- aprenda que a regra se aplica a qualquer lugar (“
Ctrl-Csairá dos programas”) - observe algumas exceções (“ok,
Ctrl-Cvai sairfindmas nãoless“) - descobrir subconscientemente qual é o padrão (“
Ctrl-Cgeralmente encerrará programas não interativos, mas em programas interativos poderá interromper a operação atual em vez de encerrar o programa”) - eventualmente, talvez formulá-lo em uma regra explícita que eu conheça
Muito do meu entendimento do terminal ainda está honestamente no estágio de “reconhecimento de padrões subconscientes”. A única razão pela qual tenho dedicado tempo para tornar as coisas explícitas é porque tenho tentado explicar como isso funciona para os outros. Esperançosamente, escrever essas “regras” explicitamente tornará o aprendizado de algumas dessas coisas um pouco mais rápido para outros.



