Complicação dos comandos do console, 1979-2020

Meu hobby é abrir a “Filosofia UNIX” de McIlroy em um monitor enquanto lê mana em outro.

O primeiro dos princípios de McIlroy é frequentemente reformulado como "Faça uma coisa, mas faça bem". Esta é uma abreviação para suas palavras: “Crie programas que fazem uma coisa bem. Para novos trabalhos, crie novos programas, em vez de complicar os antigos, adicionando novas "funções". ”

McIlroy dá um exemplo:

Parece surpreendente para os estrangeiros que os compiladores UNIX não emitem listagens: a impressão é melhor realizada e configurada com mais flexibilidade usando um programa separado.

Se você abrir a ajuda para ls, ele começará com

ls [-ABCFGHLOPRSTUW@abcdefghiklmnopqrstuwx1] [file ...]

Ou seja, os sinalizadores de uma letra para lsincluir todas as letras minúsculas, exceto {jvyz}14 letras maiúsculas @e 1. São 22 + 14 + 2 = 38 apenas opções de caractere único.

No Ubuntu 17, a ajuda para lsnão mostrará um resumo normal, mas você verá que lsexistem 58 opções (incluindo --helpe --version).

Vamos ver se esta é uma situação única ou uma situação normal. Vamos fazer uma lista de alguns comandos comuns, classificados por frequência de uso.



A tabela mostra o número de opções de linha de comando para vários comandos v7 Unix (1979), slackware 3.1 (1996), ubuntu 12 (2015) e ubuntu 17 (2017). Quanto mais parâmetros, mais escuras as células (em escala logarítmica).

Vimos que, ao longo dos anos, o número de opções aumenta drasticamente: como regra, os registros escurecem da esquerda para a direita (mais opções) e não há casos em que os registros se tornam mais claros (menos opções).

McIlroy há muito tempo condenou o aumento no número de opções, tamanho e funcionalidade geral das equipes 1:

, , Linux … []. , , . , , … Unix : « ? ?» , - , . , , , . … , .

Ironicamente, uma das razões para o crescente número de opções de linha de comando é o outro ditado de McIlroy: "Escreva programas para processar fluxos de texto porque é uma interface universal" (veja lscomo exemplo).

Se dados ou objetos estruturados foram transmitidos, a formatação pode ser deixada no estágio final. Mas no caso de texto sem formatação, formatação e conteúdo são misturados; como a formatação só pode ser feita analisando o conteúdo, os comandos geralmente adicionam opções de formatação por conveniência. Além disso, o formato pode ser realizada quando um usuário aplica seu conhecimento da estrutura de dados, e "codifica" o conhecimento dos argumentos para cut, awk,sedetc. (o usuário também usa seu conhecimento de como esses programas funcionam com a formatação, porque é diferente para diferentes programas; portanto, o usuário deve saber, por exemplo, como cut -f4 difere de awk '{ print $4 }2) Isso é muito mais complicado do que passar um ou dois argumentos para o próximo comando em uma sequência, e isso transfere a complexidade da ferramenta para o usuário.

Às vezes, as pessoas dizem que não desejam oferecer suporte a dados estruturados, porque, em uma ferramenta universal, seria necessário oferecer suporte a vários formatos. Mas eles já precisam suportar vários formatos para criar uma ferramenta universal. Alguns comandos padrão não podem ler a saída de outros comandos porque eles usam formatos diferentes. Por exemplo, o wc -wUnicode não lida corretamente com isso, etc. Dizer que "texto" é um formato universal é o mesmo que dizer que "binário" é um formato universal.

Dizem que não há alternativa para essa complicação das ferramentas de linha de comando. Mas as pessoas que dizem isso nunca tentaram uma alternativa, algo como o PowerShell. Tenho muitas reclamações sobre o PowerShell, mas a transferência de dados estruturados e a capacidade de trabalhar facilmente com dados estruturados sem ter que manter os metadados em minha mente, para que eu possa transferi-los para as ferramentas de linha de comando certas nos lugares certos no pipeline não é uma das minhas reclamações 3.

Quando você é informado de que os programas devem ser simples e compatíveis durante o processamento de texto, essas pessoas fingem que os dados de texto não têm uma estrutura para analisar 4. Em alguns casos, podemos representar tudo como uma única linha, separada por espaços, ou como uma tabela com separadores de linha e coluna ( com comportamento que, é claro, não é consistente com outras ferramentas ). Isso adiciona um pouco de dificuldade. Ainda há casos em que a serialização de dados em um formato de texto sem formatação adiciona complexidade significativa, pois, devido à estrutura de dados, a serialização simples do texto requer subseqüentemente esforços de análise significativos para re-assimilar os dados de maneira significativa.

Outro motivo pelo qual as equipes agora têm mais opções é porque as pessoas adicionaram sinalizadores convenientes para funcionalidades que poderiam ser implementadas por um pipeline de várias equipes. Essa prática vem ocorrendo desde o Unix v7, em quelsapareceu uma opção para alterar a ordem de classificação (embora isso possa ser feito passando a saída para tac).

Com o tempo, por conveniência, foram adicionados parâmetros adicionais. Por exemplo, um comando mvque estava originalmente sem parâmetros agora pode mover o arquivo e criar simultaneamente uma cópia de backup (três opções; duas maneiras diferentes de especificar um backup, uma das quais aceita um argumento e a outra não aceita argumentos, lê um argumento implícito de uma variável de ambiente VERSION_CONTROL; outra opção permite substituir o sufixo de backup padrão). Agora mvainda existem opções para nunca sobrescrever arquivos ou sobrescrever apenas arquivos mais novos.

mkdiroutro programa que anteriormente não tinha opções. Hoje, todos os seus sinalizadores, com exceção das opções de segurança para SELinux ou SMACK, bem como os números de ajuda e versão, são adicionados apenas por conveniência: definindo permissões para o novo diretório e criando diretórios pai, caso não existam.

No tailinício, havia apenas uma opção -number, indicando o ponto de partida do trabalho. Em seguida, adicionamos formatação e opções para a conveniência da formatação. O sinalizador -zsubstitui o separador de linhas por null. Aqui estão outros exemplos de opções adicionadas por conveniência: -fimprimir quando novas alterações aparecerem, -sdefinir o intervalo de tempo limite entre a verificação de alterações / código> -f e também -retrytentar acessar novamente o arquivo, se ele não estiver disponível.

McIlroy critica os desenvolvedores por adicionar todas essas opções, mas eu pessoalmente me sinto melhor. Embora eu nunca tenha usado alguns deles, raramente usei outros, mas essa é a beleza dos parâmetros da linha de comando - ao contrário da interface gráfica, adicionar esses parâmetros não atrapalha a interface. Sim, mana e ajuda a aumentar, mas na era do Google e Stackoverflow, em qualquer caso, muitos pesquisam qualquer pergunta no Google, em vez de ler mana.

Obviamente, adicionar opções aumenta a carga sobre os mantenedores. Mas este é um pagamento justo pelos benefícios que eles trazem. Dada a proporção do número de mantenedores e usuários, é lógico impor um ônus adicional ao primeiro, não ao segundo. Isso é semelhante à observação de Gary Bernhardt de que é aconselhável ensaiar uma apresentação 50 vezes. Se o público for de 300 pessoas, a proporção de tempo gasto assistindo o desempenho e o tempo gasto em ensaios ainda será de 6: 1. Para ferramentas populares de linha de comando, essa proporção é ainda mais extrema.

Alguns podem argumentar que todas essas opções adicionais sobrecarregam os usuários. Isso não está totalmente errado, mas esse fardo de complexidade sempre existirá. A questão é onde exatamente. Se você imagina que um conjunto de ferramentas de linha de comando, juntamente com um shell, forma uma linguagem na qual todos podem escrever um novo método e, em caso de popularidade, o método é efetivamente adicionado à biblioteca padrão e os padrões são determinados por axiomas como "Gravar programas para processar fluxos de texto, porque é universal interface ”, o idioma se tornará um caos incoerente somente para gravação, se você o levar por completo. Pelo menos, graças às ferramentas com uma ampla gama de opções e funcionalidades, os usuários do Unix podem substituir um conjunto gigante de ferramentas extremamente inconsistentes por apenas um grande conjunto de ferramentas que,embora sejam inconsistentes um com o outro por fora, eles têm alguma consistência interna.

McIlroy implica falta de consideração na caixa de ferramentas. Assim, os pais fundadores do Unix devem sentar-se na mesma sala e pensar com cuidado até encontrar um conjunto de ferramentas seqüenciais de "extraordinária simplicidade". Mas não será escalável, a filosofia do próprio Unix inevitavelmente levou à bagunça em que estamos. Não é que alguém não pense muito ou muito. O ponto é uma filosofia que não ultrapassa uma equipe relativamente pequena, com um entendimento cultural comum que pode caber em uma sala.

Se alguém quiser escrever uma ferramenta baseada na “filosofia Unix”, pessoas diferentes terão opiniões diferentes sobre o que “simplicidade” significa ou o princípio de “fazer uma coisa” 5como a ferramenta deve funcionar corretamente - e a inconsistência florescerá em cores exuberantes, resultando em uma enorme complexidade, semelhante a linguagens extremamente inconsistentes como o PHP. As pessoas tiram sarro do PHP e do JavaScript por várias esquisitices e inconsistências, mas, como uma linguagem e uma biblioteca padrão, qualquer shell popular com uma coleção de ferramentas * nix populares, tomadas em conjunto, é muito pior e contém uma complexidade muito mais aleatória devido a inconsistências, mesmo na mesma distribuição Linux . Não pode ser de outra maneira. Se você comparar as distribuições de Linux, BSD, Solaris, AIX, etc., a quantidade de complexidade aleatória que os usuários devem ter em mente ao trocar de sistema, obscurece a inconsistência de PHP ou JavaScript.As linguagens de programação mais amplamente ridicularizadas são exemplos reais de ótimo design em comparação a elas.

Para maior clareza, não estou dizendo que eu ou outra pessoa poderia lidar melhor com o desenvolvimento nos anos 70, levando em consideração o conhecimento disponível na época, e criar um sistema que seria útil naquele momento e elegante hoje. Obviamente, é fácil olhar para trás e encontrar problemas em retrospecto. Simplesmente discordo dos comentários de alguns conhecedores do Unix, como McIlroy, que sugerem que esquecemos ou não entendemos o valor da simplicidade. Ou Ken Thompson, que diz que C é um idioma tão seguro quanto qualquer outro e, se não queremos que apareçam erros, precisamos escrever código sem erros. Comentários desse tipo implicam que pouco mudou ao longo dos anos. Alegadamente, nos anos 70, construímos sistemas da mesma maneira que hoje e, durante cinco décadas de experiência coletiva, dezenas de milhões de pessoas-ano não nos ensinaram nada. E se nos voltarmos para as origens, para os criadores do Unix, tudo ficará bem. Com todo o respeito, não concordo.

Aplicação: memória


Embora as queixas de McIlroy sobre binários inchados estejam um pouco fora do escopo deste artigo, observarei que em 2017 comprei um Chromebook com 16 GB de RAM por US $ 300. O binário de 1 megabyte pode ser um problema sério em 1979, quando o Apple II padrão era equipado com 4 kilobytes de memória. O Apple II custou US $ 1.298 em 1979 ou US $ 4.612 em 2020. Hoje você pode comprar um Chromebook barato que custa menos de 1/15 desse preço, enquanto ele tem quatro milhões de vezes mais memória. As reclamações de que o uso da memória aumentou mil vezes parecem um pouco ridículas quando (portátil!) A máquina é muito mais barata e tem quatro milhões de vezes mais memória.

Eu gosto da otimização, então reduzi minha página inicial a dois pacotes (haveria um se o CDN suportasse brotli de alto nível), mas esse é um requisito puramente estético, eu faço isso por diversão. O gargalo das ferramentas de linha de comando não está usando memória, e o tempo para otimizar a memória da ferramenta com um tamanho de um megabyte é como reduzir uma página inicial a um pacote. Talvez um hobby divertido, mas nada mais.

Metodologia de compilação de tabela


A frequência de uso de comandos é obtida nos arquivos públicos do histórico de comandos no github, não corresponde necessariamente à sua experiência pessoal. Somente comandos "simples" foram contados, excluindo instâncias como curl, git, gcc (o último possui mais de 1000 opções) e wget. O conceito de simplicidade é relativo. Comandos internos do shell , como cd, também não foram levados em consideração.

A repetição de sinalizadores não foi considerada uma opção separada. Por exemplo, u git blame -C, git blame -C -Ce git blame -C -C -Cum comportamento diferente, mas todos eles vão ser considerado como um argumento, embora -C -Celes -C são realmente diferentes argumentos.

As subopções na tabela são contadas como uma opção. Por exemplo, ele lssuporta o seguinte:

--format=WORD across -x, commas -m, horizontal -x, long -l, single-column -1, verbose -l, vertical -C

Apesar de sete opções format, isso conta como uma opção.

As opções que são explicitamente indicadas como inúteis ainda são consideradas opções, por exemplo ls -g, o que é ignorado também é considerado.

Várias versões da mesma opção são consideradas uma opção. Por exemplo, -Ae --almost-allpara ls.

Se o certificado disser que a opção existe, mas, na realidade, ela não existe, ela não será levada em consideração. Por exemplo, a ajuda para v7 mv diz:

SACOS

Se o arquivo1 e o arquivo2 estiverem em sistemas de arquivos diferentes, o mv deverá copiar o arquivo e excluir o original. Nesse caso, o nome do proprietário se torna o nome do processo de cópia e qualquer conexão com outros arquivos é perdida.

O mv deve aceitar o sinalizador -f como rm para suprimir uma mensagem sobre a existência de um arquivo de destino que não seja gravável.

Mas -fnão é considerado um sinalizador na tabela, porque a opção realmente não existe.

A tabela termina em 2017 porque o primeiro rascunho deste artigo foi escrito. Só agora eles conseguiram ler.

Neste tópico



, , -, //.



1. Essa citação é um pouco diferente da versão comum, porque eu assisti o vídeo original . Pelo que sei, todas as cópias dessa citação na Internet (índices Bing, DuckDuckGo e Google) são retiradas da mesma transcrição de uma pessoa. Há uma certa ambiguidade, porque o som é de baixa qualidade e ouço palavras ligeiramente diferentes das que essa pessoa ouviu. [para retornar]

2. Outro exemplo de como a complexidade é passada para o usuário, porque equipes diferentes lidam com a formatação de maneira diferente, é a formatação da hora . O tempo de shell interno é time, obviamente, incompatível com /usr/bin/time. O usuário deve estar ciente desse fato e saber como lidar com ele. [para retornar]

3. Por exemplo, para qualquer objeto que você possa usar ConvertTo-Jsonou ConvertTo-CSV. Ou "cmdlets" para alterar a exibição das propriedades do objeto . Você pode gravar arquivos de configuração de formatação que definem seus métodos de formatação preferidos.

Outra maneira de ver isso é através do prisma da lei de Conway . Se tivermos um conjunto de ferramentas de linha de comando criadas por pessoas diferentes, geralmente de organizações diferentes, essas ferramentas serão extremamente inconsistentes se alguém não puder determinar o padrão e forçar as pessoas a aceitá-lo. Na verdade, isso funciona relativamente bem no Windows, não apenas no PowerShell.

Uma reclamação comum contra a Microsoft é a rotatividade massiva da API, geralmente por razões organizacionais não técnicas (por exemplo, veja as ações de Stephen Sinofsky, conforme descrito nas respostas a um tweet remoto ). É verdade. No entanto, do ponto de vista de um usuário ingênuo, o software padrão do Windows, via de regra, transmite dados não textuais muito melhores que o * nix. A cobertura de dados não textuais no Windows remonta a pelo menos COM em 1999 (e possivelmente OLE e DDE, lançado em 1990 e 1987, respectivamente).

Por exemplo, se você copiar do Foo, que suporta o formato binário, Ae Bna Barra, que suporta formatos, Be Cdepois copiar do Bar para o Baz, que suporta CeD, tudo funcionará bem, mesmo que Foo e Baz não tenham formatos suportados comuns.

Quando você recorta ou copia algo, o aplicativo basicamente “informa” a área de transferência em quais formatos ele pode fornecer dados. Quando colado em um aplicativo, o aplicativo final pode solicitar dados em qualquer um dos formatos disponíveis. Se os dados já estiverem na área de transferência, o Windows os fornecerá. Se não for esse o caso, o Windows recebe dados do aplicativo de origem e os transfere para o aplicativo de destino, e uma cópia é armazenada por algum tempo. Se você "cortar" no Excel, ele dirá "você" que ele possui dados disponíveis em várias dezenas de formatos. Esse sistema é muito bom para compatibilidade, embora certamente não possa ser chamado de simples ou minimalista.

Além do bom suporte de muitos formatos, é suficiente para que muitos programas comecem a lidar bem com esses recursos; no Windows, fora da caixa, geralmente há um bom suporte à área de transferência.

Suponha que você copie e cole uma pequena quantidade de texto. Na maioria dos casos, nenhuma surpresa ocorrerá no Windows ou no Linux. Mas agora suponha que você copiou algum texto, fechou o programa do qual copiou e o colou. Muitos usuários tendem a pensar que, ao copiar dados, eles são armazenados na área de transferência e não no programa em que são copiados. No Windows, o software geralmente é escrito de acordo com essa expectativa (embora tecnicamente os usuários da API da área de transferência não devam fazer isso). Isso é menos comum no Linux com X, onde o modelo mental correto para a maioria dos programas é que a cópia salva um ponteiro para os dados que ainda pertencem ao programa do qual estão sendo copiados. Ou seja, a inserção não funcionará se o programa estiver fechado.Quando entrevistei programadores (informalmente), eles geralmente ficaram surpresos com isso, a menos que realmente trabalhassem com a função copiar + colar para seu aplicativo. Quando entrevistei não programadores, eles geralmente acharam esse comportamento não apenas surpreendente, mas também confuso.

A desvantagem de transferir a área de transferência para o sistema operacional é que copiar grandes quantidades de dados é caro. Suponha que você copie uma quantidade muito grande de texto, muitos gigabytes ou algum tipo de objeto complexo e depois não o cole. Na verdade, você não deseja copiar esses dados do seu programa para o sistema operacional para que sejam armazenados lá e disponíveis. O Windows faz isso com sabedoria: os aplicativos podem fornecer dados sob demanda , se considerados benéficos. No nosso caso, quando o usuário fecha o programa, ele pode determinar se deve colocar os dados na área de transferência ou excluí-los. Nesse caso, muitos programas (por exemplo, o Excel) oferecerão "salvar" dados na área de transferência ou excluí-los, o que é bastante razoável.

Alguns desses recursos podem ser implementados no Linux. Por exemplo,A especificação ClipboardManager descreve o mecanismo de salvamento, e os aplicativos GNOME geralmente o suportam (embora com alguns bugs ), mas a situação no * nix realmente difere do onipresente suporte para aplicativos Windows, onde uma área de transferência competente geralmente é implementada. [para retornar]

4. Outro exemplo são as ferramentas em cima dos compiladores modernos. Vamos voltar e ver o exemplo canônico de Macilroy, onde os compiladores Unix certos são tão especializados que a listagem é feita por uma ferramenta separada. Hoje, porém, isso mudou, embora uma ferramenta de listagem separada tenha permanecido. Alguns compiladores Linux populares têm literalmente milhares de opções - e são extremamente ricos em recursos. Por exemplo, uma das muitas funções do moderno clang é a análise estática. No momento da redação deste artigo, existem 79 testes de rotina de análise estática e 44 testes experimentais.. Se fossem comandos separados, eles ainda dependeriam da mesma infraestrutura básica do compilador e imporiam a mesma carga de manutenção - não é razoável que essas ferramentas de análise estática trabalhem com texto sem formatação e redefinam toda a cadeia de ferramentas do compilador necessária para chegando ao ponto em que eles podem realizar análises estáticas. Eles podem ser comandos separados em vez de serem combinados no clang, mas ainda dependem do mesmo mecanismo e impõem a carga de manutenção e complexidade ao compilador (que deve suportar interfaces estáveis ​​para ferramentas que funcionam sobre ele), ou serão constantes quebrar.

Fazer tudo no texto por simplicidade parece bonito, mas, na verdade, a representação textual dos dados geralmente não é o que você precisa se deseja fazer um trabalho realmente útil.

O mesmo clang é simplesmente mais funcional do que qualquer compilador que existisse em 1979, ou mesmo todos os compiladores que existiam em 1979 combinados, independentemente de ter sido executado por um comando monolítico ou por milhares de instruções menores. É fácil dizer que em 1979 tudo foi mais simples e que nós, programadores modernos, nos perdemos. Mas, na realidade, é difícil oferecer um design muito mais simples e que seja verdadeiramente aceito por todos. É impossível que esse design possa preservar toda a funcionalidade e configurabilidade existente e ser tão simples quanto algo de 1979. [para retornar]

5. Desde a sua criação, o curl passou de suportar três protocolos para 40. Isso significa que ele "faz 40 coisas" e a filosofia do Unix exige que ele seja dividido em 40 comandos separados? Depende de quem perguntar. Se cada protocolo fosse sua própria equipe, criada e apoiada por outra pessoa, teríamos a mesma bagunça que as equipes. Parâmetros de linha de comando inconsistentes, formatos de saída inconsistentes, apesar de todos serem fluxos de texto, etc. Isso nos aproxima da simplicidade que McIlroy defende? Depende de quem perguntar. [para retornar]

All Articles