parece que Bash é uma linguagem completa de Turing
O conceito de Turing completude é totalmente separado de muitos outros conceitos úteis em uma linguagem de programação na grande : usabilidade, expressividade, understandabilty, velocidade, etc.
Se Turing-completude foram todos nós necessário, não teríamos quaisquer linguagens de programação em tudo , nem mesmo linguagem assembly . Todos os programadores de computador escreveriam apenas no código da máquina , já que nossas CPUs também são completas em Turing.
por que o Bash é usado quase exclusivamente para escrever scripts relativamente simples?
Scripts shell grandes e complexos - como os configure
scripts produzidos pelo GNU Autoconf - são atípicos por vários motivos:
Até relativamente recentemente, você não podia contar com um shell compatível com POSIX em todos os lugares .
Muitos sistemas, principalmente os mais antigos, tecnicamente possuem um shell compatível com POSIX em algum lugar do sistema, mas pode não estar em um local previsível /bin/sh
. Se você está escrevendo um script de shell e ele precisa ser executado em muitos sistemas diferentes, como então você escreve a linha shebang ? Uma opção é seguir em frente e usar /bin/sh
, mas opte por se restringir ao dialeto shell Bourne anterior ao POSIX, caso ele seja executado em um sistema desse tipo.
Os reservatórios Bourne pré-POSIX nem possuem aritmética embutida; você precisa chamar expr
ou bc
fazer isso.
Mesmo com um shell POSIX, você está perdendo matrizes associativas e outros recursos que esperamos encontrar nas linguagens de script Unix desde que o Perl se tornou popular no início dos anos 90 .
Esse fato da história significa que há uma tradição de décadas em ignorar muitos dos recursos poderosos dos modernos interpretadores de scripts da família Bourne, puramente porque você não pode contar com eles em todos os lugares.
Na verdade, isso ainda continua até hoje: o Bash não conseguiu matrizes associativas até a versão 4 , mas você pode se surpreender com a quantidade de sistemas ainda em uso baseados no Bash 3. A Apple ainda envia o Bash 3 com o macOS em 2017 - aparentemente para motivos de licenciamento - e os servidores Unix / Linux geralmente são executados praticamente sem tocar por muito tempo, portanto, você pode ter um sistema antigo estável ainda executando o Bash 3, como uma caixa do CentOS 5. Se você possui esses sistemas em seu ambiente, não pode usar matrizes associativas em scripts de shell que precisam ser executados neles.
Se sua resposta para esse problema é que você apenas escreve scripts de shell para sistemas "modernos", precisa lidar com o fato de que o último ponto de referência comum para a maioria dos shells do Unix é o padrão de shell POSIX , que permanece praticamente inalterado desde que foi introduzido em 1989. Existem muitas conchas diferentes com base nesse padrão, mas todas divergiram em graus variados desse padrão. Para tirar arrays associativos de novo, bash
, zsh
, e ksh93
todos têm essa característica, mas existem várias incompatibilidades de implementação. Sua escolha, então, é usar apenas o Bash, ou apenas o Zsh, ou apenas o uso ksh93
.
Se a sua resposta para esse problema for "instale o Bash 4" ou ksh93
, o que for, por que não "instale" o Perl, o Python ou o Ruby? Isso é inaceitável em muitos casos; os padrões importam.
Nenhuma das linguagens de script de shell da família Bourne suporta módulos .
O mais próximo que você pode chegar de um sistema de módulo em um script de shell é o .
comando - também conhecido source
em variantes de shell Bourne mais modernas - que falha em vários níveis em relação a um sistema de módulo apropriado, o mais básico dos quais é o namespacing .
Independentemente da linguagem de programação, o entendimento humano começa a sinalizar quando qualquer arquivo único em um programa geral maior excede alguns milhares de linhas. O motivo pelo qual estruturamos programas grandes em muitos arquivos é para que possamos abstrair seu conteúdo em uma ou duas frases, no máximo. O arquivo A é o analisador de linha de comando, o arquivo B é a bomba de E / S da rede, o arquivo C é o calço entre a biblioteca Z e o restante do programa, etc. Quando seu único método para reunir muitos arquivos em um único programa é a inclusão de texto , você limita o tamanho dos seus programas para crescer razoavelmente.
Para comparação, seria como se a linguagem de programação C não tivesse vinculador, apenas #include
instruções. Esse dialeto C-lite não precisaria de palavras-chave como extern
ou static
. Esses recursos existem para permitir modularidade.
O POSIX não define uma maneira de definir variáveis de escopo para uma única função de script de shell, muito menos para um arquivo.
Isso efetivamente torna todas as variáveis globais , o que prejudica a modularidade e a composição.
Existem soluções para este em conchas de pós-POSIX - certamente bash
, ksh93
e zsh
pelo menos - mas isso só traz de volta ao ponto 1 acima.
Você pode ver o efeito disso nos guias de estilo na gravação de macro do GNU Autoconf, onde eles recomendam que você prefixe os nomes das variáveis com o nome da própria macro, levando a nomes de variáveis muito longos apenas para reduzir a chance de colisão de maneira aceitável perto de zero.
Mesmo C é melhor nessa pontuação, por uma milha. Além de a maioria dos programas C serem escritos principalmente com variáveis locais de função, C também oferece suporte ao escopo de blocos, permitindo que vários blocos em uma única função reutilizem nomes de variáveis sem contaminação cruzada.
As linguagens de programação do shell não possuem biblioteca padrão.
É possível argumentar que a biblioteca padrão de uma linguagem de script de shell é o conteúdo de PATH
, mas que apenas diz que, para obter alguma conseqüência, um script de shell precisa chamar outro programa inteiro, provavelmente um escrito em uma linguagem mais poderosa para começar com.
Também não existe um arquivo amplamente usado de bibliotecas de utilitários de shell, como no CPAN do Perl . Sem uma grande biblioteca disponível de código de utilitário de terceiros, um programador deve escrever mais código manualmente, para que seja menos produtivo.
Mesmo ignorando o fato de que a maioria dos shell scripts dependem de programas externos normalmente escritos em C para obter alguma coisa útil fazer, há a sobrecarga de todos aqueles pipe()
→ fork()
→ exec()
cadeias de chamadas. Esse padrão é bastante eficiente no Unix, comparado ao IPC e ao processo iniciado em outros sistemas operacionais, mas aqui está efetivamente substituindo o que você faria com uma chamada de sub - rotina em outra linguagem de script, que é muito mais eficiente ainda. Isso coloca um limite sério no limite superior da velocidade de execução de scripts de shell.
Os scripts de shell têm pouca capacidade interna de aumentar seu desempenho via execução paralela.
Shells Bourne tem &
, wait
e dutos para isso, mas isso é em grande parte apenas útil para compor vários programas, não para alcançar CPU ou I / paralelismo S. É provável que você não consiga identificar os núcleos ou saturar uma matriz RAID apenas com scripts de shell e, se o fizer, provavelmente poderá obter um desempenho muito maior em outros idiomas.
Os pipelines, em particular, são maneiras fracas de aumentar o desempenho via execução paralela. Ele permite apenas que dois programas sejam executados em paralelo, e um dos dois provavelmente será bloqueado na E / S de / para o outro a qualquer momento.
Há maneiras dos últimos dias em torno deste, como xargs -P
e GNUparallel
, mas isto só recai para o ponto 4 acima.
Com efetivamente nenhuma capacidade embutida de tirar o máximo proveito dos sistemas com vários processadores, os scripts de shell sempre serão mais lentos do que um programa bem escrito em uma linguagem que pode usar todos os processadores do sistema. Para pegar o configure
exemplo de script GNU Autoconf novamente, dobrar o número de núcleos no sistema fará pouco para melhorar a velocidade na qual ele é executado.
As linguagens de script do shell não têm ponteiros ou referências .
Isso impede que você faça várias coisas facilmente em outras linguagens de programação.
Por um lado, a incapacidade de se referir indiretamente a outra estrutura de dados na memória do programa significa que você está limitado às estruturas de dados internas . Seu shell pode ter matrizes associativas , mas como elas são implementadas? Existem várias possibilidades, cada uma com diferentes vantagens: árvores vermelho-pretas , árvores AVL e tabelas de hash são as mais comuns, mas existem outras. Se você precisar de um conjunto diferente de vantagens e desvantagens, ficará sem dinheiro porque, sem referências, não há como manipular manualmente muitos tipos de estruturas de dados avançadas. Você está preso ao que recebeu.
Ou pode ser que você precise de uma estrutura de dados que nem sequer tenha uma alternativa adequada incorporada ao seu interpretador de script de shell, como um gráfico acíclico direcionado , necessário para modelar um gráfico de dependência . Eu tenho sido programação por décadas, e a única maneira que eu posso pensar em fazer isso em um shell script seria abusar do sistema de arquivos , usando links simbólicos como referências falsas. Esse é o tipo de solução que você obtém quando confia apenas na integridade de Turing, que não diz nada sobre se a solução é elegante, rápida ou fácil de entender.
Estruturas de dados avançadas são apenas um uso para ponteiros e referências. Existem vários outros aplicativos para eles , o que simplesmente não pode ser feito facilmente em uma linguagem de script de shell da família Bourne.
Eu poderia continuar, mas acho que você está entendendo o ponto aqui. Simplificando, existem muitas linguagens de programação mais poderosas para sistemas do tipo Unix.
Essa é uma enorme vantagem, que poderia compensar a mediocridade da própria linguagem em alguns casos.
Claro, e é exatamente por isso que o GNU Autoconf usa um subconjunto intencionalmente restrito da família Bourne de linguagens de script shell para suas configure
saídas de script: para que seus configure
scripts sejam executados praticamente em todos os lugares.
Você provavelmente não encontrará um grupo maior de crentes na utilidade de escrever em um dialeto Bourne shell altamente portátil do que os desenvolvedores do GNU Autoconf, mas sua própria criação é escrita principalmente em Perl, além de alguns m4
, e apenas um pouco de shell roteiro; somente a saída do Autoconf é um script shell Bourne puro. Se isso não implora a questão de quão útil é o conceito "Bourne em todos os lugares", não sei o que será.
Então, existe um limite para a complexidade de tais programas?
Tecnicamente falando, não, como sugere a observação de Turing-completeness.
Mas isso não é o mesmo que dizer que scripts de shell arbitrariamente grandes são agradáveis de escrever, fáceis de depurar ou rápidos de executar.
É possível escrever, digamos, um compressor / descompressor de arquivos no bash puro?
Bash "puro", sem chamadas para as coisas no PATH
? O compressor provavelmente é possível usando echo
seqüências de escape hexagonais, mas seria bastante doloroso. Pode ser impossível escrever o descompactador dessa maneira devido à incapacidade de manipular dados binários no shell . Você acabaria chamando od
e traduzindo dados binários para o formato de texto, a maneira nativa do shell de manipular dados.
Depois que você começa a falar sobre o uso de scripts de shell da maneira que se pretendia, como cola para direcionar outros programas PATH
, as portas se abrem, porque agora você está limitado apenas ao que pode ser feito em outras linguagens de programação, ou seja, você não tem limites. Um script shell que recebe todo o seu poder, chamando a outros programas no PATH
não correr tão rápido como programas monolíticas escritos em linguagens mais poderosas, mas não executado.
E esse é o ponto. Se você precisa de um programa para executar rapidamente, ou se precisa ser poderoso por si só, em vez de emprestar energia de outras pessoas, não o escreve com casca.
Um simples videogame?
Aqui está Tetris com casca . Outros jogos estão disponíveis, se você for procurar.
existem apenas ferramentas de depuração muito limitadas
Eu colocaria o suporte à ferramenta de depuração em 20º lugar na lista de recursos necessários para dar suporte à programação em geral. Muitos programadores confiam muito mais na printf()
depuração do que nos depuradores apropriados, independentemente da linguagem.
No shell, você tem echo
e set -x
, que juntos são suficientes para depurar muitos problemas.
sh
scriptconfigure
usado como parte do processo de compilação para muitos pacotes un * x não é 'relativamente simples'.