Por que os utilitários obrigatórios do POSIX não estão embutidos no shell?

45

O objetivo desta pergunta é responder a uma curiosidade, não resolver um problema de computação específico. A pergunta é: Por que os utilitários obrigatórios do POSIX não costumam ser incorporados às implementações de shell?

Por exemplo, eu tenho um script que basicamente lê alguns arquivos de texto pequenos e verifica se eles estão formatados corretamente, mas leva 27 segundos para executar, na minha máquina, devido a uma quantidade significativa de manipulação de strings. Essa manipulação de strings cria milhares de novos processos chamando vários utilitários, daí a lentidão. Estou bastante confiante de que se algumas das utilidades foram construídos em, ou seja grep, sed, cut, tr, e expr, em seguida, o script seria executado em um segundo ou menos (com base na minha experiência em C).

Parece que haveria muitas situações em que a criação desses utilitários faria a diferença entre uma solução no shell script ter ou não um desempenho aceitável.

Obviamente, há uma razão pela qual ele foi escolhido para não incorporar esses utilitários. Talvez ter uma versão de um utilitário no nível do sistema evite que várias versões desiguais desse utilitário sejam usadas por vários shells. Realmente não consigo pensar em muitas outras razões para manter a sobrecarga de criação de tantos processos novos, e o POSIX define o suficiente sobre os utilitários, para que não pareça um problema ter implementações diferentes, desde que cada POSIX compatível. Um problema pelo menos não tão grande quanto a ineficiência de ter tantos processos.

Kyle
fonte
15
Se 27 segundos forem muito lentos, você poderá usar Python, Perl ou alguma outra linguagem semi-compilada. Em alternativa, publique as partes lentas do seu script e peça melhorias. Pode ser que você esteja usando três ou quatro comandos, onde um (mais rápido) pode ser usado.
roaima
8
Infelizmente, os shells não foram feitos para tarefas pesadas, infelizmente e o mundo mudou muito desde os tempos em que você podia se dar bem com apenas um script de shell. Eu concordo com o roaima - todo administrador de sistema razoável deve usar o Python ou o Perl e não esperar que o shell lide com tudo #
Sergiy Kolodyazhnyy
16
O objetivo principal do shell é executar outros programas, não manipular dados diretamente. Ao longo dos anos, alguns programas ou recursos externos fornecidos por eles (globbing, aritmética printfetc.) foram incorporados aos shells quando foram considerados úteis o suficiente.
Chepner
8
Se você postar seu script no codereview.stackexchange.com, tenho certeza de que os revisores poderão fazer algumas sugestões para acelerar seu script drasticamente (ou pelo menos apontar por que ele deve ser escrito em Python / etc em vez de shell).
Chepner
5
@Kyle: awké um utilitário obrigatório em POSIX, e especialmente adequado (isto é, muito rápido) para implementar scripts que você poderia implementar usando sed, cut, tr, grep, e exprem um shell script.
Animal Nominal

Respostas:

11

Não é esperado que scripts de shell sejam executados com esse tipo de velocidade. Se você quiser melhorar a velocidade do seu script, tente em perl. Se isso ainda for muito lento, você precisará mudar para uma linguagem de tipo estaticamente, como java ou c, ou escrever um módulo C para perl que execute as partes que são muito lentas.

O Shell é o primeiro nível de prototipagem. Se você puder provar o conceito com o shell, vá para uma linguagem de script melhor, que pode fazer mais verificações de limites, o que exigiria muitos hectares de shell.

Espera-se que um sistema operacional Unix inclua muitos programas pequenos, que executam tarefas bem definidas que compõem uma imagem maior. Isso é bom, pois compartimenta programas maiores. Dê uma olhada no qmail, por exemplo, e compare com o sendmail. O qmail é composto por muitos programas:

http://www.nrg4u.com/qmail/the-big-qmail-picture-103-p1.gif

A exploração do daemon de rede não ajudaria a explorar o gerenciador de filas.

Ed Neville
fonte
O OP especificamente NÃO pediu sugestões para melhorar a velocidade do código. A questão era por que certos utilitários não são incorporados como cdou pwd.
Stephen C
4
Verdadeiro. A resposta foi expressar a diferença entre monolítico e compartimentalizado e mostrar uma razão a esse favor.
Ed Neville
Veja também: askubuntu.com/a/291926/11751
um CVn
1
O @StephenC cdé um builtin - e realmente precisa ser, porque alterar o diretório de trabalho em um subprocesso não afeta os processos pai.
Jonas
67

Por que os utilitários obrigatórios do POSIX não estão embutidos no shell?

Como para ser compatível com POSIX, é necessário um sistema 1 para fornecer a maioria dos utilitários como comandos independentes.

Tê-los embutidos implicaria que eles deveriam existir em dois locais diferentes, dentro e fora da concha. Obviamente, seria possível implementar a versão externa usando um wrapper de script de shell para o builtin, mas isso prejudicaria aplicativos não shell que chamam os utilitários.

Observe que o BusyBox seguiu o caminho sugerido implementando muitos comandos internamente e fornecendo a variante autônoma usando links para si. Um problema é que, embora o conjunto de comandos possa ser bastante grande, as implementações geralmente são um subconjunto do padrão, portanto não são compatíveis.

Note também que, pelo menos ksh93, bashe zshir mais longe, fornecendo métodos personalizados para o shell correndo para builtins carregar dinamicamente a partir de bibliotecas compartilhadas. Tecnicamente, nada impede que todos os utilitários POSIX sejam implementados e disponibilizados como componentes internos.

Finalmente, a geração de novos processos tornou-se uma operação bastante rápida nos sistemas operacionais modernos. Se você realmente for atingido por um problema de desempenho, pode haver algumas melhorias para tornar seus scripts mais rápidos.

1 POSIX.1-2008

No entanto, todos os utilitários padrão , incluindo os embutidos regulares na tabela, mas não os embutidos especiais descritos em Utilitários Internos Especiais, devem ser implementados de maneira a poderem ser acessados ​​através da família exec de funciona como definido no volume System Interfaces do POSIX.1-2008 e pode ser chamado diretamente pelos utilitários padrão que a exigem (env, find, nice, nohup, time, xargs).

jlliagre
fonte
4
Esta é a resposta certa, mas gostaria de acrescentar que, como a interface desses utilitários geralmente é via stdin / stdout de qualquer maneira, mesmo que todos eles também tenham sido implementados como uma rotina interna no bash, ele ainda precisará efetivamente bifurcar-se e criar tubos para cada comando em um pipeline de qualquer maneira, então não seria apenas ganhos marginais
Chunko
2
@Chunko Yes. os subshells são mais leves que os processos fork / exec'ed.
Jlliagre
3
@slebetman Você está perdendo o meu ponto. Subshells não são threads nem processos executados, independentemente de estarem em execução no Linux ou não. Subshells são apenas o clone de seus pais, criado por um fork não seguido por exec; forkHoje em dia é uma operação muito leve em comparação com exec.
Jlliagre
3
Eu medi os built-in do busybox noforkcomo tendo na ordem de 10x menos sobrecarga do que os noexecbuilt-in, que por sua vez tinham ~ 5x menos sobrecarga que o fork + exec de um binário separado. Definições de acordo com unix.stackexchange.com/a/274322/29483 É interessante que o busybox não faça noforktudo, embora eu saiba que algum código do busybox é encurtado por não limpar a memória e apenas se baseie em um processo de curta duração.
sourcejedi
1
@ jlliagre: No linux, um fork cria um processo. O ponto que talvez você esteja perdendo é que, no Linux, eles otimizaram tanto os processos que os desenvolvedores determinaram que não há mais vantagens em criar algo mais leve. Basicamente, no Linux, um processo é tão leve quanto um thread.
23817 slebetman
9

No manual de referência do BASH ,

Os comandos internos são necessários para implementar funcionalidades impossíveis ou inconvenientes de serem obtidas com utilitários separados.

Como tenho certeza de que você já ouviu falar, a filosofia do UNIX depende muito de vários aplicativos com funcionalidades limitadas. Cada embutido tem uma boa razão para ser embutido. Todo o resto não é. Eu acho que uma classe de perguntas mais interessante está na linha de "por que exatamente está pwd embutido?"

Stephen C
fonte
2
Em uma palavra: Modularity
Peschke 23/02
2
/ bin / pwd existe. Eu acho cdque seria um exemplo melhor aqui de algo que é impossível de implementar como uma ferramenta separada.
Oskar Skog
1
@OskarSkog Esse era o ponto. cdtem que ser construído, pwdnão. Então, por que os bashimplementadores optaram por incluí-lo?
Stig Hemmer
1
... que é coberto por unix.stackexchange.com/questions/145479 .
JdeBP
O @StigHemmer /bin/bashexiste, mas ainda está embutido. Veja a lista de builtins em gnu.org/software/bash/manual/html_node/…
Stephen C
8

Os caras da AT&T se perguntaram a mesma coisa

Se você observar o histórico do AT&T Software Toolkit (atualmente inativo no github desde que a equipe principal partiu), foi exatamente isso que eles fizeram com o shell K&N da AT&T, também conhecido como ksh93.

O desempenho sempre fez parte da motivação dos mantenedores do ksh93 e, ao criar o ksh, você pode optar por criar muitos utilitários POSIX comuns como bibliotecas carregadas dinamicamente. Ao vincular esses comandos a um nome de diretório como /opt/ast/bin, você pode controlar em qual versão do comando será usada, com base na posição desse nome de diretório $PATH.

Exemplos:

cat chmod chown cksum cmp cp cut date expr fmt head join ln
mkdir mkfifo mktemp mv nl od paste rm tail tr uniq uuencode wc

A lista completa pode ser encontrada no repositório do github ast .

Observe que a maioria das ferramentas ast tem sua própria proveniência e diferem fortemente das implementações mais comuns do gnu. A equipe de pesquisa da AT&T obedeceu aos padrões oficiais, que eram a maneira de obter interoperabilidade quando não era possível compartilhar código.

Henk Langeveld
fonte
6

Portanto, não organizamos recursos para otimizar a ferramenta original, para atender a todos os desejos específicos. Acho que o que precisamos explicar é quanto esse desejo específico teria custado para ser implementado.

O POSIX define o suficiente sobre os utilitários para que não pareça um grande problema ter implementações diferentes.

esta é uma suposição ruim :-P.

Os sistemas pós-POSIX continuam a se tornar mais poderosos e convenientes por boas razões; como um padrão pós-fato, ele nunca alcança.

O Ubuntu iniciou um esforço para mudar para um shell POSIX simplificado para scripts, para otimizar o antigo processo de inicialização do System V init. Não estou dizendo que falhou, mas disparou muitos bugs que precisavam ser limpos: "bashisms", scripts que foram executados sob a /bin/shsuposição de que os bashrecursos estavam disponíveis.

O POSIX sh não é uma boa linguagem de programação de uso geral. Seu principal objetivo é funcionar bem como um shell interativo. Assim que você começar a salvar seus comandos em um script, lembre-se de se aproximar de um tarpit de Turing . Por exemplo, não é possível detectar falhas no meio de um pipeline normal . bashadicionado set -o pipefailpara isso, mas isso não está no POSIX.

Recursos úteis, mas não padronizados, similares são fornecidos por quase todos os utilitários mais complexos que o true.

Para a classe de tarefa que você descreve, você pode desenhar uma linha aproximada para Awk, Perl e hoje em dia Python. Diferentes ferramentas foram criadas e evoluídas de forma independente. Você esperaria, por exemplo, que o GNU Awk fosse incluído em um arquivo de extensão estendido?

Não estou dizendo que agora temos uma abordagem universalmente melhor para a qual posso apontar. Eu tenho um fraquinho por Python. O Awk é surpreendentemente poderoso, embora eu tenha ficado frustrado com alguns recursos específicos do GNU Awk. Mas o ponto é que o processamento de um grande número de strings individualmente (presumivelmente a partir das linhas dos arquivos) não era um objetivo de design do shell POSIX.

sourcejedi
fonte
Eu me pergunto se haveria alguma dificuldade com um shell que assumiria que qualquer comando executado a partir de uma lista configurável de locais seria tratado como embutido nos casos em que o shell entendesse tudo sobre o comando? Se um script executar, cat -@fnord fooo shell deve decidir que, uma vez que não sabe o que -@significa que seria necessário chamar o comando real, mas, dado que cat <foo >baro shell não deve gerar outro processo.
26717
1
@supercat complexidade.
sourcejedi
2

Há também a questão de: Em qual shell você o construiria?

A maioria dos sistemas Unix / Linux possui vários shells diferentes que são desenvolvidos independentemente (sh / bash / korn / ???). Se você criar as ferramentas no shell, você terminará com uma implementação diferente dessas ferramentas para cada shell. Isso causaria sobrecarga, e você poderá ter diferentes recursos / bugs, por exemplo, grep, dependendo do shell usado para invocá-lo.

MTilsted
fonte
O zsh é bastante popular em alguns círculos atualmente. O csh / tcsh historicamente teve muitos seguidores, mas acho que você não vê muito disso hoje. E há todo um conjunto de conchas menos conhecidos ...
um CVn
Modularidade. Com os builtins, você precisa recompilar ou reinstalar o shell cada vez que uma alteração é feita em um deles.
Can-ned_food
1

Muitos responderam bem. Pretendo apenas elogiar essas respostas. Eu acho que a filosofia do UNIX é que uma ferramenta deve fazer uma coisa e fazê-lo bem. Se alguém tenta criar uma ferramenta abrangente, há muito mais espaço para falhas. Limitar a funcionalidade dessa maneira torna um conjunto de ferramentas confiável.

Além disso, considere, se funcionalidades como sed ou grep foram incorporadas ao shell, seria tão fácil chamar a partir da linha de comando quando você quiser?

Para concluir, considere que algumas das funcionalidades que você deseja estar no BASH estão no BASH . Por exemplo, a capacidade de correspondência de ER no BASH é implementada usando o operador binário = ~ (consulte Gramática do Shell na página de manual para obter mais informações, consulte a discussão da construção [[]] para if ). Como um exemplo muito rápido, digamos que estou pesquisando um arquivo com 2 dígitos hexadecimais:

while read line; do
    if [[ $line =~ 0x[[:xdigit:]]{2} ]]; then
        # do something important with it
    fi
done < input_file.txt

Quanto à funcionalidade do tipo sed , procure em Expansão de parâmetro no cabeçalho de expansão da mesma página do manual. Você verá muitas coisas que você pode fazer que lembram sed. Costumo usar o sed para fazer alterações no tipo de substituição no texto. Com base no exposto acima:

# this does not take into account the saving of the substituted text
# it shows only how to do it
while read line; do
    ${line/pattern/substitution}
done < input_file.txt

No final, porém, o acima é "melhor" do que?

grep -E "[[:xdigit:]]{3}" input_file.txt
sed -e 's/pattern/substitution/' input_file.txt
Andrew Falanga
fonte
Um argumento contra a última pergunta pode ser encontrado em unix.stackexchange.com/questions/169716/…
phk
1

Acho que é um acidente histórico.

Quando o UNIX foi criado no final da década de 1960 e no início da década de 1970, os computadores não tinham tanta memória quanto hoje. Na época, teria sido possível implementar toda essa funcionalidade como shell embutido, mas devido a limitações de memória, eles teriam que limitar a quantidade de funcionalidade que poderiam implementar, ou arriscar a falta de memória e / ou trocar a lixeira problemas

Por outro lado, implementando a funcionalidade fornecida como programas separados e fazendo com que os dois sistemas necessários solicitem o início de um novo processo o mais leve possível, eles podem criar um ambiente de script que não tem esses problemas e ainda é executado a um preço razoável. Rapidez.

Obviamente, uma vez que essas coisas são implementadas como processos separados, as pessoas as iniciam a partir de programas que não são cascas e, em seguida, precisam permanecer assim, ou de repente todo esse software começa a quebrar.

Isso não quer dizer que você não possa implementar alguma funcionalidade duas vezes, no entanto, e de fato alguns shells implementam alguma funcionalidade que deveria ser um programa externo como um shell embutido; Por exemplo, o bash implementa o echocomando como um builtin, mas também há um/usr/bin/echo

Wouter Verhelst
fonte